Commit f7284b9e by Joshua Clayton Committed by Prem Sichanugrist

Pass aditional parameters to S3 expiring urls

This adds functionality to add additional params to the querystring for
S3 expiring URLs. The reason for this is if you want to override
response_content_type or response_content_disposition with expiring
URLs, you have to change Amazon's signature, which gets signed before
adding additional options to the querystring. Because it's added later,
and because Amazon's signature includes the full URL, the signature is
bad and the request fails.

To use this feature:

    has_attached_file :avatar,
                      :s3_url_options => { :response_content_disposition => "inline" }

Additionally, you can pass a lambda and it'll be evaluated when the URL
is generated. If, for example, the content type is incorrect in Amazon
(either empty or application/octet-stream), you can effectively cast the
response from Amazon as a particular content type.

    has_attached_file :avatar,
                      :s3_url_options => lambda {|model| { :response_content_type => model.avatar_content_type } }
parent 4e4fa9d4
...@@ -141,7 +141,8 @@ module Paperclip ...@@ -141,7 +141,8 @@ module Paperclip
def expiring_url(time = 3600, style_name = default_style) def expiring_url(time = 3600, style_name = default_style)
if path if path
s3_object(style_name).url_for(:read, :expires => time, :secure => use_secure_protocol?(style_name)).to_s base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
end end
end end
...@@ -159,6 +160,12 @@ module Paperclip ...@@ -159,6 +160,12 @@ module Paperclip
@s3_host_alias @s3_host_alias
end end
def s3_url_options
s3_url_options = @options[:s3_url_options] || {}
s3_url_options = s3_url_options.call(instance) if s3_url_options.is_a?(Proc)
s3_url_options
end
def bucket_name def bucket_name
@bucket = @options[:bucket] || s3_credentials[:bucket] @bucket = @options[:bucket] || s3_credentials[:bucket]
@bucket = @bucket.call(self) if @bucket.is_a?(Proc) @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
......
...@@ -350,16 +350,24 @@ class S3Test < Test::Unit::TestCase ...@@ -350,16 +350,24 @@ class S3Test < Test::Unit::TestCase
end end
context "Generating a secure url with an expiration" do context "Generating a secure url with an expiration" do
setup do def build_model_with_options(options = {})
rebuild_model :storage => :s3, base_options = {
:s3_credentials => { :storage => :s3,
:production => { :bucket => "prod_bucket" }, :s3_credentials => {
:development => { :bucket => "dev_bucket" } :production => { :bucket => "prod_bucket" },
}, :development => { :bucket => "dev_bucket" }
:s3_host_alias => "something.something.com", },
:s3_permissions => "private", :s3_host_alias => "something.something.com",
:path => ":attachment/:basename.:extension", :s3_permissions => "private",
:url => ":s3_alias_url" :path => ":attachment/:basename.:extension",
:url => ":s3_alias_url"
}
rebuild_model base_options.merge(options)
end
should "use default options" do
build_model_with_options
rails_env("production") rails_env("production")
...@@ -373,8 +381,40 @@ class S3Test < Test::Unit::TestCase ...@@ -373,8 +381,40 @@ class S3Test < Test::Unit::TestCase
@dummy.avatar.expiring_url @dummy.avatar.expiring_url
end end
should "should succeed" do should "allow overriding s3_url_options" do
assert true build_model_with_options :s3_url_options => { :response_content_disposition => "inline" }
rails_env("production")
@dummy = Dummy.new
@dummy.avatar = StringIO.new(".")
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:url_for).with(:read, :expires => 3600, :secure => true, :response_content_disposition => "inline")
@dummy.avatar.expiring_url
end
should "allow overriding s3_object options with a proc" do
build_model_with_options :s3_url_options => lambda {|attachment| { :response_content_type => attachment.avatar_content_type } }
rails_env("production")
@dummy = Dummy.new
@file = StringIO.new(".")
@file.stubs(:original_filename).returns("5k.png\n\n")
@file.stubs(:content_type).returns("image/png\n\n")
@file.stubs(:to_tempfile).returns(@file)
@dummy.avatar = @file
object = stub
@dummy.avatar.stubs(:s3_object).returns(object)
object.expects(:url_for).with(:read, :expires => 3600, :secure => true, :response_content_type => "image/png")
@dummy.avatar.expiring_url
end end
end end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment