【问题标题】:How can I force url friendly filenames when using CarrierWaveDirect in Ruby on Rails?在 Ruby on Rails 中使用 CarrierWaveDirect 时,如何强制使用 url 友好的文件名?
【发布时间】:2013-11-07 20:34:48
【问题描述】:

我正在使用 CarrierWaveDirect 来处理图片上传。我有用户上传文件名不友好的文件,例如:

one arizona center.jpg

当我在控制台中找到相应的照片对象时,路径显示为:

p.image.path
=> "uploads/8da5058e-6037-41d4-b311-094aaabf5469/one arizona center.jpg"

这一切都很好,因为我可以使用 image_url 方法显示图像并获取:

.../uploads/84cf32df-40eb-462b-88cb-ecc9452d2727/one%20arizona%20center.jpg

问题是照片对象是否已更新。在我的情况下,管理员用户必须批准照片。该对象仅具有一个用于“已批准”的布尔字段,该字段被切换。当我跑步时:

p.update_attribute(:approved,true)

路径变为:

p.image.path
=> "uploads/8da5058e-6037-41d4-b311-094aaabf5469/one%2520arizona%2520center.jpg"

以下是实际控制器操作的样子:

  def approve
    photo = Photo.find(params[:id])
    status = photo.update_attribute :approved, true

    respond_to do |wants|
      ...
    end
  end

这是来自服务器日志的示例:

Started POST "/admin/photos/7/approve" for 199.223.122.34 at 2013-10-29 20:46:18 +0000
2013-10-29T20:46:18.589054+00:00 app[web.1]: Processing by Admin::PhotosController#approve as */*
2013-10-29T20:46:18.589054+00:00 app[web.1]:   Parameters: {"id"=>"7"}
2013-10-29T20:46:18.598598+00:00 app[web.1]:   Photo Load (4.2ms)  SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 LIMIT 1  [["id", "7"]]
2013-10-29T20:46:18.603564+00:00 app[web.1]:    (4.6ms)  BEGIN
2013-10-29T20:46:18.610299+00:00 app[web.1]:   Photo Load (3.8ms)  SELECT "photos".* FROM "photos" WHERE "photos"."id" = $1 LIMIT 1  [["id", 7]]
2013-10-29T20:46:18.613560+00:00 app[web.1]:    (2.3ms)  UPDATE "photos" SET "approved" = 't', "image" = '0289da71-14cd-46c1-a42c-feff92ac0303/Screen%2520Shot%25202013%252010%252029%2520at%25201.44.20%2520PM.png', "updated_at" = '2013-10-29 20:46:18.610428' WHERE "photos"."id" = 7

请注意,在最后的 SQL 语句中,“image”属性和“approved”属性都已更新。现在,在将 image_url 方法提供给图像标签时,不安全的 url 在本地数据库中被转义,导致网站上的图像损坏。

有什么方法可以轻松地使用 CarrierWaveDirect 强制 url 安全文件名?

更多信息我的上传者如下:

class ImageUploader < CarrierWave::Uploader::Base

  include CarrierWaveDirect::Uploader
  include Sprockets::Helpers::RailsHelper
  include Sprockets::Helpers::IsolatedHelper

  include CarrierWave::MimeTypes
  process :set_content_type

  def extension_white_list
    %w(jpg jpeg png)
  end

end

安装了上传器的模型如下所示:

class Photo < ActiveRecord::Base
  has_one :business, foreign_key: "eponic_id", primary_key: "business_id"
  attr_accessible :image, :description
  mount_uploader :image, ImageUploader
  scope :approved, where(approved: true)
  scope :pending, where(approved: false)
end

更新:

我们无法确定问题的确切原因。显然,CarrierWave 或 CarrierWaveDirect 有一个回调,当我在上传者的父模型上运行 update_attribute 时会调用该回调。作为一种解决方法,我只是使用“update_column”来跳过 rails 的回调链。我在 github 上提交了一个问题: https://github.com/dwilkie/carrierwave_direct/issues/113

【问题讨论】:

    标签: ruby-on-rails ruby file-upload amazon-s3 carrierwave


    【解决方案1】:

    我猜测每次提交更新表单时,图像 url 都会一次又一次地转义。这只是表明您需要在表单中提供未转义的 URL。

    如果用户提交了'images/my image name.jpg' 的url,他们在表单刷新时应该永远不会看到'images/my%20image%20%name.jpg',即使这是您在内部存储它的方式。您应该对其进行解码以进行显示 - 只需在将其输出到表单时将其传递给 URI.decode

    (这与在表单中包含不安全的 HTML 字符串不同——这实际上只是一个表示用户输入的问题,就像它最初没有 URI 编码一样。&amp;lt; 字符,例如,仍然会在您的页面上成为 &amp;lt; 实体,但不是 %3C 转义序列。Rails 应该自动处理 HTML 安全性——只要绝对确定并尝试 URL 中的一些标签。)

    编辑:不想带你走一些 XSS 花园小路,我试过了...

    %input{ type: :text, value: URI.decode("http://%3Cscript%3Ealert(1);%3C/script%3E.jpg") }
    

    没关系。它在文本框中显示为 http://&lt;script&gt;alert(1);&lt;/script&gt;.jpg,但在源代码中它是 HTML 安全的。

    根据您的 cmets 我决定从头开始设置一个小应用程序,看看会发生什么。这是一个简单的 Rails 4 应用程序。我引入了carrierwave gem 并创建了一个默认上传器。 (我将在单独的步骤中查看carrierwave_direct)。我创建了一个Photo 模型如下:

    class Photo < ActiveRecord::Base
      attr_accessible :image, :description, :approved
      mount_uploader :image, PhotoUploader
    end
    

    PhotoUploader 类看起来像这样(生成器的所有默认值):

    class PhotoUploader < CarrierWave::Uploader::Base
      storage :file
    
      def store_dir
        "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
      end
    end
    

    在我接近任何表单之前,让我们尝试从控制台创建一个Photo 实例,重新读取它,更新一个属性,然后看看我们的路径会发生什么。我有一个名为picture of neil.jpg 的文件位于应用程序的根目录中。

    2.0.0-p195 :001 > p = Photo.new
     => #<Photo id: nil, image: nil, description: nil, approved: nil, created_at: nil, updated_at: nil> 
    2.0.0-p195 :002 > p.image.class
     => PhotoUploader 
    

    到目前为止一切顺利。让我们伪造一个表单哈希,然后给上传者一个数据的文件句柄,然后保存。

    2.0.0-p195 :012 > p.image = { file_name: 'picture of neil.jpg', content_type: 'image/jpeg', size: File.size('picture of neil.jpg') }
     => {:file_name=>"picture of neil.jpg", :content_type=>"image/jpeg", :size=>53637} 
    2.0.0-p195 :013 > p.image = File.open 'picture of neil.jpg'
     => #<File:picture of neil.jpg>
    2.0.0-p195 :014 > p.save!
      (0.2ms)  begin transaction
      SQL (1.4ms)  INSERT INTO "photos" ("created_at", "image", "updated_at") VALUES (?, ?, ?)  [["created_at", Tue, 29 Oct 2013 18:03:49 UTC +00:00], ["image", "picture_of_neil.jpg"], ["updated_at", Tue, 29 Oct 2013 18:03:49 UTC +00:00]]
      (1.6ms)  commit transaction
    => true 
    

    现在,如果我查看 public/uploaders/photo/image/1,我会在一个名为 picture_of_neil.jpg 的文件中找到我。 Carrierwave 已经对名称进行了消毒,但没关系。它没有将空格转换为%20 字符或类似的东西。

    我将调整文件名清理程序正则表达式以接受空格并再次尝试:

    2.0.0-p195 :015 > CarrierWave::SanitizedFile.sanitize_regexp = /[^a-zA-Z0-9_\.\-\+ ]/
     => /[^a-zA-Z0-9_\.\-\+ ]/ 
    2.0.0-p195 :016 > p = Photo.new
     => #<Photo id: nil, image: nil, description: nil, approved: nil, created_at: nil, updated_at: nil> 
    2.0.0-p195 :017 > p.image = { file_name: 'picture of neil.jpg', content_type: 'image/jpeg', size: File.size('picture of neil.jpg') }
     => {:file_name=>"picture of neil.jpg", :content_type=>"image/jpeg", :size=>53637} 
    2.0.0-p195 :018 > p.image = File.open 'picture of neil.jpg'
     => #<File:picture of neil.jpg> 
    2.0.0-p195 :019 > p.save!
       (0.2ms)  begin transaction
      SQL (0.6ms)  INSERT INTO "photos" ("created_at", "image", "updated_at") VALUES (?, ?, ?)  [["created_at", Tue, 29 Oct 2013 18:12:09 UTC +00:00], ["image", "picture of neil.jpg"], ["updated_at", Tue, 29 Oct 2013 18:12:09 UTC +00:00]]
       (1.3ms)  commit transaction
     => true 
    

    很酷,它保存了名称中带有空格的文件,没问题。如果我调整另一个属性并再次保存它,它就像预期的那样好。致电update_attributes 也可以。

    carrierwave_direct 使用:fog,所以在我做任何其他事情之前,我将切换到使用雾(在其出色的模拟模式下)并再次尝试我刚刚完成的所有操作。我的新载波初始化器如下所示:

    Fog.mock!
    
    S3_CREDENTIALS = { provider:              'AWS',
                       region:                'eu-west-1',
                       aws_access_key_id:     'MOCKKEYMOCKKEY', 
                       aws_secret_access_key: 'MOCKSECRETMOCKSECRET' }
    
    S3_DIRECTORY = 'mock_bucket'
    
    # If you're using Fog in mock mode, you have to create an in-memory directory.
    Fog::Storage.new(S3_CREDENTIALS).directories.create(key: S3_DIRECTORY, public: false) if Fog.mocking?
    
    CarrierWave::SanitizedFile.sanitize_regexp = /[^a-zA-Z0-9_\.\-\+ ]/
    
    CarrierWave.configure do |config|
      config.storage = :fog
      config.fog_credentials = S3_CREDENTIALS
      config.fog_directory   = S3_DIRECTORY
    end
    

    现在我将完成创建Photo 实例的步骤,看看会发生什么。我不会重复控制台输出,因为它是相同的。现在我可以打电话给p.image.url,看看雾给我带来了什么:

    2.0.0-p195 :005 > p.image.url
     => "https://s3.amazonaws.com/mock_bucket/uploads/photo/image/5/picture%20of%20neil.jpg"
    

    好的!这正是我们所期望的。 Photo 实例保存的路径不包含这些%20 字符;它包含空格。但是,该 URL 已被转义。这一切都很好。如果我更新说明会怎样?

    2.0.0-p195 :006 > p.update_attributes description: "A picture of me in a hat"
       (0.3ms)  begin transaction
      SQL (0.5ms)  UPDATE "photos" SET "description" = ?, "updated_at" = ? WHERE "photos"."id" = 5  [["description", "A picture of me in a hat"], ["updated_at", Tue, 29 Oct 2013 18:29:34 UTC +00:00]]
       (2.1ms)  commit transaction
     => true 
    2.0.0-p195 :007 > p.reload
      Photo Load (0.4ms)  SELECT "photos".* FROM "photos" WHERE "photos"."id" = ? LIMIT 1  [["id", 5]]
     => #<Photo id: 5, image: "picture of neil.jpg", description: "A picture of me in a hat", approved: nil, created_at: "2013-10-29 18:26:57", updated_at: "2013-10-29 18:29:34"> 
    2.0.0-p195 :008 > p.image.url
     => "https://s3.amazonaws.com/mock_bucket/uploads/photo/image/5/picture%20of%20neil.jpg" 
    

    一切都还好。现在,如果以某种方式将文件名设置为 url 而不是原始文件名,您可以看到这将如何反复转义和重新转义。但正如您所说,您只是在更新一个字段。

    让我们引入carrierwave_direct 来看看它的作用。我的PhotoUploader 类现在看起来像这样:

    class PhotoUploader < CarrierWave::Uploader::Base
      include CarrierWaveDirect::Uploader
    end
    

    我已经完成了创建上传表单的记录步骤。

    ARGH,此时我被难住了,因为它在模拟模式下似乎与 Fog 不兼容。它会将我引导至真正的 s3 服务器,因此无法进行更新我的新模型的往返。对不起。稍后我将通过控制台尝试相同的操作,当我有更多时间时。

    好的,通过控制台让它工作。希望足以看到发生了什么。我不得不静音验证,但请看下面 - 我正在重现您所看到的行为。

    2.0.0-p195 :027 > class CarrierWaveDirect::Validations::ActiveModel::FilenameFormatValidator < ::ActiveModel::EachValidator
    2.0.0-p195 :028?>    def validate_each(record, attribute, value)
    2.0.0-p195 :029?>     end
    2.0.0-p195 :030?>   end
     => nil 
    2.0.0-p195 :031 > class CarrierWaveDirect::Validations::ActiveModel::RemoteNetUrlFormatValidator < ::ActiveModel::EachValidator
    2.0.0-p195 :032?>    def validate_each(record, attribute, value)
    2.0.0-p195 :033?>     end
    2.0.0-p195 :034?>   end
     => nil 
    2.0.0-p195 :035 > p = Photo.new
     => #<Photo id: nil, image: nil, description: nil, approved: nil, created_at: nil, updated_at: nil> 
    2.0.0-p195 :036 > p.image = { file_name: 'picture of neil.jpg', content_type: 'image/jpeg' }
     => {:file_name=>"picture of neil.jpg", :content_type=>"image/jpeg"} 
    2.0.0-p195 :037 > p.image = File.open 'picture of neil.jpg'
     => #<File:picture of neil.jpg> 
    2.0.0-p195 :038 > p.save!
       (0.1ms)  begin transaction
      Photo Exists (0.2ms)  SELECT 1 AS one FROM "photos" WHERE "photos"."image" = '1383074454-35950-4053/picture%2520of%2520neil.jpg' LIMIT 1
      SQL (2.2ms)  INSERT INTO "photos" ("created_at", "image", "updated_at") VALUES (?, ?, ?)  [["created_at", Tue, 29 Oct 2013 19:20:56 UTC +00:00], ["image", "1383074454-35950-4053/picture%2520of%2520neil.jpg"], ["updated_at", Tue, 29 Oct 2013 19:20:56 UTC +00:00]]
       (2.5ms)  commit transaction
     => true 
    2.0.0-p195 :039 > p.image.url
     => "https://s3.amazonaws.com/mock_bucket/uploads/1383074454-35950-4053/picture%252520of%252520neil.jpg" 
    

    显然这个 url 是错误的——无论出于什么原因,它已经被转义了太多次了。请注意,我没有看到在更新属性时重复发生这种情况。我要补充的是carrierwave_direct 文档说您必须在创建该对象时设置image.key,例如

    2.0.0-p195 :048 > p.image.key = "1383074454-35950-4053/picture%20of%20neil.jpg"
     => "1383074454-35950-4053/picture%20of%20neil.jpg" 
    

    这可以正常工作,并且 url 不会被双重转义。您是在创建步骤中执行此操作,还是使用标准carrierwave 将使用的多部分格式方法?

    【讨论】:

    • 这就是问题所在。我从不将其输出到表单中。我只是有一条路线/photos/approve/:id。我们在方法中所做的只是 Photo.find(params[:id]).update_attribute(:approved, true) 但这随后会生成以下 SQL:(1.7ms) UPDATE "photos" SET "approved" = 't', "image" = '02216914-1e9e-4b63-8ae6-80e24cb87f7a/5918%25201%2520photo.jpg', "updated_at" = '2013-10-29 05:31:31.779058' 其中 "photos"."id" = 302
    • 我不知道为什么在 photo.update_attribute(:approved, true) 期间更新“图像”属性。
    • 你能发布你的模型吗(至少是验证和任何生命周期挂钩?)
    • 已添加。那里没有什么太花哨的事情。我还在carrierwave direct的github问题上发布了这个,因为我想知道这里是否存在错误。
    • 我最近使用了很多 Carrierwave,我将生成一个非常简单的应用程序,看看我是否遇到了同样的事情。
    猜你喜欢
    • 1970-01-01
    • 2021-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-02
    • 2010-10-16
    • 1970-01-01
    相关资源
    最近更新 更多