<input type="file">をJavaScriptで扱うときに気をつけること

  • JavaScript
  • HTML

この記事はJavaScript Advent Calendar 2020の25日目になります。

レビューサイトを始めとする「ユーザーが画像ファイルをそれぞれがアップロードして投稿するウェブサイト」を作るとき、必ず用いられるのが<input type="file">によるアップロードです。

一般的にファイルのアップロードといえば、PHPやSQLといったバックエンド技術が主役のように捉えられがちです。
しかし、取得した画像ファイルを用いたサムネイルの生成や画像ファイルの軽量化など、JavaScriptの出番も少なくありません。

実際に<input type="file">を取り扱い、いくつかハマりやすいと感じたポイントがあったため、この場を借りてまとめていきます。

1. <input type="file">valueは、画像データではない

<input>要素は、入力されたテキストをステータスをvalueの中に格納し、保持します。
<input type="file">を例外ではなく、valueで値を受け取り、valueを空にすることでデータを削除することができます。 しかし、そのvalue内部をconsole.logで確認すると、値は画像データではありませんでした。

C:\fakepath\image.jpg

<input type="file">valueに保存されるのは、画像のデータではなくパスです。
取得した画像データはvalueではなく、filesの中に配列で格納されます。
画像のプレビューなど、取得した画像をページ上に再描写させる機能を実装する際には、valueではなくfilesのデータを用いることになります。

また、<input type="file">valueにはアクセス制約あるため、取得した画像ファイルのフルパスは取得できません。
各ブラウザによって取得されるパスの形式は異なりますが、大体の場合「C:/fakepath/」などのダミーパスが表示されます。
もし、ファイル名をページ上に表示させる画面を作りたいのであれば、ファイル名の取得には、valueではなくfiles[i].nameを用いたほうがよいでしょう。

同じくセキュリティの観点から、valueそのものを修正することも禁止されています。

2. <input type="file">で取得した画像データは、置き換えできない

filesにはFileコンストラクタがFileListという名の配列になって格納されています。

FileList {0: File, length: 1}
  0: File
    lastModified: 1607151722901
    lastModifiedDate: Sat Dec 05 2020 16:02:02 GMT+0900 (日本標準時) {}
    name: "image.jpg"
    size: 145455
    type: "image/jpeg"
    webkitRelativePath: ""
  length: 1

Fileは、new File()とすることで生成できるため、一見上書きが可能なように見えます。
しかし、実際にfilesを故意にFileで上書きしようとすると失敗します。

Uncaught TypeError: Failed to set an indexed property on 'FileList': Index property setter is not supported.

これも、<input type="file">のセキュリティが原因です。

画像圧縮などでどうしてもデータを更新したいときには、画像をbase64に変換し、<input type="hidden">など別のパーツにデータを保持させれば解決できます。
<input type="file">に残っている元の画像データは、送信されないように削除しておくことを忘れずに。

また、データが挿入できるということは、改竄もできるということです。
valueの中身が改竄されたり、意図していないデータが送信されるリスクも存在します。
そのため、バックエンド側でのバリデーションや送信できるデータのサイズ制限は必須となります。

まとめ

個人的に、フォーム関連パーツのHTMLとJavaScriptはなかなか融通が聞かない印象です。
難問にぶつかったとき、それを無理やり解決するのではなく、仕様をしっかり読み解いていくことを意識していきたいですね。

参考