我猜每次您提交更新表单时,图像网址都会一次又一次地转义。这只是表明您需要在表单中提供未转义的 URL。
如果用户提交了一个 url,'images/my image name.jpg'
他们应该永远不会看到'images/my%20image%20%name.jpg'
表单刷新的时间,即使这是您在内部存储它的方式。您应该对其进行解码以进行显示-只需在将其URI.decode
输出到表单时将其传递即可。
(这与在表单中包含不安全的 HTML 字符串不同——这实际上只是一个问题,即完全按照最初没有 URI 编码的方式表示<
用户输入。例如,一个字符仍然会成为<
页面上的实体,但不是%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
使用:fog
so 在我做任何其他事情之前,我将切换到使用雾(在其出色的模拟模式下)并尝试我刚刚完成的所有操作。我的新载波初始化器如下所示:
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 将使用的多部分形式方法?