11

I'm trying to attach CSV files to a Rails3 model using paperclip 4.1.1, but I'm having trouble getting the content-type as reported by S3 to be text/csv (instead I am getting text/plain). When I subsequently download the file from S3, the extension is getting changed to match the content-type instead of preserving the original extension (so test.csv is downloaded as test.txt).

From what I can see, when you upload a file, the FileAdapter will cache the content-type on creation with whatever value was determined by the ContentTypeDetector (which calls file -b --mime filename). Unfortunately, CSV files return text/plain which makes sense, as how can you really distinguish this? Attempts to set the content-type with attachment.instance_write(:content_type, 'text/csv') only set the value in the model and do not affect what gets written to S3.

FileAdapter's content_type initialized here: https://github.com/thoughtbot/paperclip/blob/v4.0/lib/paperclip/io_adapters/file_adapter.rb#L14

Call which creates that io_adapter: https://github.com/thoughtbot/paperclip/blob/v4.0/lib/paperclip/attachment.rb#L98

I really have a generic upload here (so I can't hard-code the content type in the S3 headers definition in has_attached_file), and I don't really want the content-type spoofing protection. Any ideas/suggestions? I would prefer not to downgrade to 3.5 because it would mean just delaying the pain, but if that's the only way, I'll entertain it...

bheeshmar
  • 3,125
  • 1
  • 19
  • 18
  • 1
    Are you using s3 or s3 through fog for your storage provider? You could pass a lambda into the s3_headers to determine if you should overwrite the content_type value, but the fog implementation doesn't have an equivalent :fog_headers option... – stereoscott May 29 '14 at 01:32
  • @stereoscott If I use S3 through Fog, do I have any other ways to override the header? – The Lazy Log Jun 16 '14 at 04:58
  • @charinten hoping my answer below helps you. – stereoscott Jun 19 '14 at 02:21

2 Answers2

14

If you are using fog then you can do something like this:

has_attached_file :report,
  fog_file: lambda { |attachment|
    {
      content_type: 'text/csv',
      content_disposition: "attachment; filename=#{attachment.original_filename}",
    }
  }

If you are using Amazon S3 as your storage provider, then something like this should work:

has_attached_file :report
  s3_headers: lambda { |attachment|
    { 
      'Content-Type' => 'text/csv',
      'Content-Disposition' => "attachment; filename=#{attachment.original_filename}",
    }
  }
stereoscott
  • 13,309
  • 4
  • 33
  • 34
  • In your setup, do you mean that we can only upload 'text/csv'? What about other format? – The Lazy Log Jun 19 '14 at 10:03
  • I think you have an extra comma at the end of the 'Content-Disposition' lines. The s3_headers section works fine if using s3 with fog. – FuzzyJulz Aug 13 '14 at 01:47
  • 1
    `attachment` instance here is the model itself, taking the example above - `original_filename` here is a method or could be an attribute in your model. – Marvin Apr 04 '18 at 11:25
  • 1
    Almost there... but the `attachment` is empty entity (_before save_) when sending to s3, so `attachment.original_filename` is always nil. How could I access the original filename before it's saved? – Maris May 23 '19 at 06:18
  • I have the exact same issue. Did you figure out a workaround? – Zain Zafar Aug 14 '20 at 14:33
0

Had this problem just recently and both the post process and the lambda don't work so did a work around. Same with others observation, the values of the attachment is empty when calling the s3 lambda headers.

  1. add this line to the model
attr_accessor :tmp_content_type, :tmp_file_name
  1. override the file assignment method so we could get the file info and store it for later use
 def file=(f)
  set_tmp_values(f.path)
  file.assign(f)
end

def set_tmp_values(file_path)
  self.tmp_file_name = File.basename(file_path)
  self.tmp_content_type = MIME::Types.type_for(file_path).first.content_type
end
  1. Use the temp vars
:s3_headers => lambda { |attachment|
  {
    'Content-Type' => attachment.tmp_content_type,
    "Content-Disposition" => "attachment; filename=\"# {attachment.tmp_file_name}\""
  }
}