0

我正在使用 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

4

1 回答 1

3

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

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

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

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

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

没关系。它在文本框中显示为http://<script>alert(1);</script>.jpg,但在源代码中它是 HTML 安全的,正如预期的那样。

根据您的评论,我决定从头开始设置一个小应用程序,看看会发生什么。这是一个简单的 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使用:fogso 在我做任何其他事情之前,我将切换到使用雾(在其出色的模拟模式下)并尝试我刚刚完成的所有操作。我的新载波初始化器如下所示:

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

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

,在这一点上,我很难过,因为它在模拟模式下似乎与 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 将使用的多部分形式方法?

于 2013-10-29T01:18:20.390 回答