0

画像のアップロードを処理するために CarrierWaveDirect を使用しています。次のような URL フレンドリーではないファイル名を持つファイルをアップロードするユーザーがいます。

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

アップデート:

問題の正確な原因を特定できませんでした。アップローダの親モデルで update_attribute を実行すると、CarrierWave または CarrierWaveDirect にコールバックが呼び出されるようです。回避策として、単に「update_column」を使用してレールのコールバック チェーンをスキップしています。github に問題を提出しました: https://github.com/dwilkie/carrierwave_direct/issues/113

4

1 に答える 1

3

更新フォームを送信するたびに、画像の URL が何度もエスケープされていると思います。これは、エスケープされていない 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もOKです。

carrierwave_direct他のことをする前に、fog を (優れたモック モードで)使用する:fogように切り替えて、今行ったことをすべてもう一度試します。新しい Carrierwave イニシャライザは次のようになります。

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"

Ok!これはまさに私たちが期待していることです。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 に設定された場合、これがどのように繰り返しエスケープされ、再エスケープされるかがわかります。しかし、あなたが言うように、1 つのフィールドだけを更新しています。

それが何をするか見てみましょうcarrierwave_direct。私PhotoUploaderのクラスは次のようになります。

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

アップロード フォームを作成するための文書化された手順を確認しました。

ARGH、この時点で私は困惑しています。なぜなら、モック モードの Fog ではうまく動作しないようだからです。実際の s3 サーバーに誘導されるため、新しいモデルを更新するための往復は発生しません。ごめん。もう少し時間があれば、後でコンソールから同じことを試してみます。

OK、コンソール経由で動作しました。うまくいけば、何が起こっているのかを見るのに十分です。検証をミュートする必要がありましたが、以下を見てください。あなたが見ている動作を再現しています。

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 は二重にエスケープされません。作成段階でこれを行っていますか、それとも標準的な搬送波が使用するマルチパート形式のアプローチを使用していますか?

于 2013-10-29T01:18:20.390 に答える