0
1

More than 3 years have passed since last update.

Railsチュートリアル 第13章 ユーザーのマイクロポスト - 画像の検証

Posted at

アップローダーにおけるバリデーションの実装

画像フォーマットのバリデーション

現状では、画像として無効なファイルをアップロードできてしまう

Railsチュートリアル 第13章 ユーザーのマイクロポスト - 基本的な画像アップロードで実装した画像のアップローダーでは、画像として無効なファイルをアップロードすることができてしまいます。

Started POST "/microposts" for 172.17.0.1 at 2020-01-08 09:50:59 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by MicropostsController#create as HTML
  Parameters: {...略, "micropost"=>{"content"=>"upload invalid format file", "picture"=>#<ActionDispatch::Http::UploadedFile:0x00007fd16c2513a0 @tempfile=#<Tempfile:/tmp/RackMultipart20200108-15302-1ati1vk.drawio>, @original_filename="Untitled Diagram.drawio", @content_type="application/octet-stream", @headers="Content-Disposition: form-data; name=\"micropost[picture]\"; filename=\"Untitled Diagram.drawio\"\r\nContent-Type: application/octet-stream\r\n">}, "commit"=>"Post"}
  User Load (6.8ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (22.5ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at", "picture") VALUES (?, ?, ?, ?, ?)  [["content", "upload invalid format file"], ["user_id", 1], ["created_at", "2020-01-08 09:51:00.104135"], ["updated_at", "2020-01-08 09:51:00.104135"], ["picture", "Untitled_Diagram.drawio"]]
   (17.5ms)  commit transaction
Redirected to http://localhost:8080/
Completed 302 Found in 194ms (ActiveRecord: 46.9ms)

「Untitled Diagram.drawio」というのは、明らかに画像ファイルではありません。しかし、それでも上記のようにマイクロポストの投稿は正常に完了してしまうのです。

このときのフィードの内容は以下のようになります。

スクリーンショット 2020-01-08 18.53.32.png

「Untitled diagram」という文字になっている部分が、画像として無効なファイルを描画しようとした成れの果てです。

CarrierWaveの機能により、特定の拡張子のファイルしかアップロードできないようにする

実は、CarrierWaveによって作られたPictureアップローダーには、「特定の拡張子のファイルしかアップロードできないようにする」という機能があります。app/uploaders/picture_uploader.rbのソースコード中にも、当該機能を使うためのコードがコメントアウトされた状態で存在します。

app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  # ...略

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # def extension_whitelist
  #   %w(jpg jpeg gif png)
  # end

  # ...略
end

当該コードを以下のように変更すれば、指定した拡張子のファイルしかアップロードできないようになります。

app/uploaders/picture_uploader.rb
  class PictureUploader < CarrierWave::Uploader::Base
    ...略

    # Add a white list of extensions which are allowed to be uploaded.
    # For images you might use something like this:
-   # def extension_whitelist
-   #   %w(jpg jpeg gif png)
-   # end
+   def extension_whitelist
+     %w(jpg jpeg gif png)
+   end

    ...略
  end

上記の例の場合、.jpg/.jpeg/.gif/.png以上4つの拡張子をもつファイルのみがアップロード可能になります。

モデルにおけるバリデーションの実装

画像のファイルサイズに対するバリデーション

ファイルサイズに対するバリデーションは、独自に実装する必要がある

Railsチュートリアル本文には、「ファイルサイズに対するバリデーションはRails標準のバリデーション1としては用意されておらず、独自に定義する必要がある」とあります。まずはファイルサイズに対するバリデーションの実体そのものを定義する必要があります。

今回のサンプルアプリケーションでは、画像はマイクロポストに関連付けされます。そのため、画像のファイルサイズに対するバリデーションもMicropostモデルに実装されるのが自然です。

ファイルサイズに対するバリデーションの実装

メソッド名はpicture_sizeとします。定義する箇所は、app/models/micropost.rbprivateメソッド以降の部分となります。

def picture_size
  if picture.size > 5.megabytes
    errors.add(:picture, "should be less than 5MB")
  end
end

picture_sizeメソッドは、「アップロードされた画像のファイルサイズが5MBを超える場合、バリデーションを失敗させ、errorsコレクションにカスタマイズされたエラーメッセージを追加する」という動作をします。

errorsコレクションの使い方の詳細については、バリデーションエラーに対応する - Active Record バリデーション - Railsガイドを参照ください。

ファイルサイズに対してバリデーションを行うようにする

上述picutre_sizeバリデーションをapp/models/micropost.rb内でvalidateメソッドによって呼び出すようにすれば、ファイルサイズに対してバリデーションが行われるようになります。

validatesではありません。validateです。私は一度ここを間違えました。)

validate :picture_size

独自に定義したバリデーションに対してvalidateメソッドで呼び出しを行う場合、validateメソッドの第1引数には、「当該バリデーション名のシンボル」を与えます。

ファイルサイズのバリデーションの実装に必要となる、Micropostモデルに対する変更の全体像

上記を踏まえ、app/models/micropost.rb全体の変更内容は以下のようになります。

app/models/micropost.rb
  class Micropost < ApplicationRecord
    belongs_to :user
    default_scope -> { order(created_at: :desc) }
    mount_uploader :picture, PictureUploader
    validates :user_id, presence: true
    validates :content, presence: true, length: { maximum: 140 }
+   validate  :picture_size
+
+   private
+
+     # アップロードされた画像のサイズをバリデーションする
+     def picture_size
+       if picture.size > 5.megabytes
+         errors.add(:picture, "should be less than 5MB")
+       end
+     end
  end

ビューに対するバリデーションの実装

ファイルアップロード用のフォーム部品に、受理するファイル形式を追加する

ファイルアップロード用のフォーム部品で特定のファイル形式のみを受理するようにするためには、file_fieldタグにacceptオプションを与えて使います。

<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>

受理するファイル形式はMIMEタイプで記述します。カンマ区切りで複数記述することができます。

大きすぎるファイルサイズに対して警告を出すようにする

Javascript(正確にはjQuery)を用いて実装してみます。

$('#micropost_picture').bind('change', function() {
  var size_in_megabytes = this.files[0].size/1024/1024;
  if (size_in_megabytes > 5) {
    alert('Macimum file size is 5MB. Please choose a smaller file.');
  }
});

上記Javascriptコードの処理内容は以下のようになります。

  • CSS idのmicropost_pictureを含む要素を見つけ出す
    • マイクロポスト投稿フォームの画像アップロード用パーツを指す
  • 上記要素に変化があった場合、このJavascriptが動作する
  • ファイルサイズが5MB以上となった場合、警告メッセージを出す

マイクロポスト投稿フォームの実装の変更

ここまでに書かれたことを総合すると、app/views/shared/_micropost_form.html.erbの変更の全体像は以下のようになります。

app/views/shared/_micropost_form.html.erb
  <%= form_for(@micropost) do |f| %>
    <%= render 'shared/error_messages', object: f.object %>
    <div class="field">
      <%= f.text_area :content, placeholder: "Compose new micropost..." %>
    </div>
    <%= f.submit "Post", class: "btn btn-primary" %>
    <span class="picture">
-     <%= f.file_field :picture %>
+     <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
    </span>
  <% end %>
+
+ <script type="text/javascript">
+   $('#micropost_picture').bind('change', function() {
+     var size_in_megabytes = this.files[0].size/1024/1024;
+     if (size_in_megabytes > 5) {
+       alert('Macimum file size is 5MB. Please choose a smaller file.');
+     }
+   });
+ </script>

ビューに対するバリデーションの限界

もっとも、警告を無視して大きすぎるアップロードを強行することは可能です。そのようなアップロードの強行を防ぐような実装も可能ですが、それでも限界はあります。例えば、以下のような操作をされた場合、ビュー側のバリデーションでは対策のしようがありません。

  • Webブラウザのインスペクター機能でJavaScriptをいじる
  • フォームを使わずに、curlなどで直接POSTリクエストを実行する

このように、ビューに対するバリデーションには限界があります。ビューに対するバリデーションの限界を超えるためには、モデルやアップローダー段階でのバリデーションが必要となるのです。


  1. presencelength等が該当します。 

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1