質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.52%

【 PHP】アップロード制限の方法

受付中

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 406

Masa-Y

score 18

メールフォームにファイルアップロード機能を実装する練習をしています。
1 index.phpで入力→
2 confirm.phpに遷移、送信。→送信しました。
という流れですが、
ファイルサイズに制限をかけて、
画像を選びなおすようにエラー表示させたいです。
ファイル選択は必須項目としているので、
サイズが大きすぎますというメッセージを表示しつつ、
制限値内の画像を選びなおさないと、2の確認画面に進めないようにしたいです。

【試したこと】
イメージ説明
上のindex.phpで、inputのMAX_FILE_SIZEを入れることで
value値より大きい画像は無効になりましたが、
confirm.phpへは進めてしまい、画像0KBとして送信できてしまいます。

この場合、どのような方法があるでしょうか。

【confirm.php】
<?php

function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

// 変数の初期化
$page_flag = 0;

if( !empty($_POST['confirm']) ) {
    $page_flag = 1;

    try {
        // 未定義である・複数ファイルである・$_FILES Corruption 攻撃を受けた
        // どれかに該当していれば不正なパラメータとして処理する
        if (!isset($_FILES['upfile']['error']) || !is_int($_FILES['upfile']['error'])) {
            throw new RuntimeException('パラメータが不正です');
        }
        // $_FILES['upfile']['error'] の値を確認
        switch ($_FILES['upfile']['error']) {
            case UPLOAD_ERR_OK: // OK
                break;
            case UPLOAD_ERR_NO_FILE:   // ファイル未選択
                throw new RuntimeException('ファイルが選択されていません');
            case UPLOAD_ERR_INI_SIZE:  // php.ini定義の最大サイズ超過
            case UPLOAD_ERR_FORM_SIZE: // フォーム定義の最大サイズ超過 (設定した場合のみ)
                throw new RuntimeException('ファイルサイズが大きすぎます');
            default:
                throw new RuntimeException('その他のエラーが発生しました');
        }
        // ここで定義するサイズ上限のオーバーチェック
        // (必要がある場合のみ)
        if ($_FILES['upfile']['size'] > 1000000) {
            throw new RuntimeException('ファイルサイズが大きすぎます');
        }
        // $_FILES['upfile']['mime']の値はブラウザ側で偽装可能なので
        // MIMEタイプに対応する拡張子を自前で取得する
        if (!$ext = array_search(
            mime_content_type($_FILES['upfile']['tmp_name']),
            array(
                'gif' => 'image/gif',
                'jpg' => 'image/jpeg',
                'png' => 'image/png',
            ),
            true
        )) {
            throw new RuntimeException('ファイル形式が不正です');
        }
        // ファイルデータからSHA-1ハッシュを取ってファイル名を決定し,保存する
        if (!move_uploaded_file(
            $_FILES['upfile']['tmp_name'],
            $path = sprintf('./files/%s.%s',
                sha1_file($_FILES['upfile']['tmp_name']),
                $ext
            )
        )) {
            throw new RuntimeException('ファイル保存時にエラーが発生しました');
        }
        // ファイルのパーミッションを確実に0644に設定する
        chmod($path, 0644);
        echo 'ファイルは正常にアップロードされました';
    } catch (RuntimeException $e) {
        echo $e->getMessage();
    }

イメージ説明
イメージ説明

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+2

\[ PHP \] アップロードファイルサイズに制限をかける ( MAX_FILE_SIZE ) – 行け!偏差値40プログラマー
サイズはゼロでも、エラーのステータスをセットで確認するしかないのでは。

イメージ説明

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/10 18:28

    回答ありがとうございます。参考にしてみます。

    キャンセル

+1

MAX_FILE_SIZEを越えると0になるのはそういう仕様なので、
MAX_FILE_SIZEをもう少し大きくして手動で制限サイズによるバリデーションをかけてチェックするしかないと思います。

※0KBファイルってそれはそれで危険なので弾いても良いと思います。

追加質問があるようなので追記。


ファイルがサーバーに保存されるのもこのタイミングではなく、次のページにしたい

ファイルの実体はinput type=fileがform送信された時点でテンポラリファイルとして既に送信されています。なので「確認画面から送信完了画面にいくときに保存」ということは基本的にできません。
1つのやり方としては「一時ディレクトリ」と「本番ディレクトリ」で分けるやり方です。

move_uploaded_file()を記述するタイミングが誤っているのでしょうか。

move_uploaded_file()にこだわる開発者が多いのは仕方がないことではありますが、、
上記のとおりmove_uploaded_file()を実行しなくてもテンポラリファイルアップロードはフォームのSUBMIT時点で行われているので
move_uploaded_file()がやっていることって実質ファイルのリネームです。
私が提示したmpywさんの記事にmove_uploaded_file じゃなくて rename じゃだめ?というのがあります。

そもそもPHPマニュアルにあるように アップロードされたファイルを新しい位置に移動するのがmove_uploaded_file()です。
「アップロードを行う関数」ではありません。関数をよく読んでみましょう。
move uploaded fileですからね。ファイルのアップロード自体はこの関数関係ないです。アップロードされたファイルをどう扱うか。なので。

ということで、テンポラリファイルに対してバリデーションを行って弾いて再入力を促すようにすること(入力画面再描画が望ましい)、
バリデーションOKのファイルであればファイルを一時フォルダに移動し、確認画面を表示
→送信ボタンを押下で本番用フォルダに移動
という流れです。

ちなみに確認画面でキャンセルとか画面閉じたりしたら一時フォルダにゴミがたまっていくので、
一時フォルダに保存する際はセッションで一意な名前のファイル名をつけておくとかして、利用者と紐づくようにしておき、削除するような仕組みが必要になります。
日時バッチ用意して全削除でも良いですけどそのときに誰も確認画面で送信しようとしてないという保証はないのでやはり紐づけは必要。でもやっぱり定期にクロールして削除する仕組みは必要。
間違ってもtmp_nameをhiddenに置いておくなんてことをしてはいけません。テンポラリなので(そこは調べてください)
もちろんnameをそのまま保存するのもいけません。(参考

というか、そもそも提示のコードって私が提示したmpywさんの記事ほぼそのままなので、それぞれがどういう意図で組まれているのかを理解するのが先のように思います。
自身のやりたいことや既にあるコードにそのまま埋め込むのはおそらく無理です。全ての機能を採用するかどうかも検討が必要ですし、いったん自身のコードから離れて記事のコードをミニマムコードに採用して使ってみて挙動確認や調整をしてからでないと使いこなせないと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/10 18:28

    回答ありがとうございます。参考にしてみます。

    キャンセル

-1

Javascriptを使って、以下のように実装することができました。
自分自身メールフォームはPHPという固定観念があったように思います
何かあればアドバイスいただけると助かります、
どうもありがとうございました。

イメージ説明
イメージ説明

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/11 14:29

    質問なのであれば文章も本文に追記いただきたいところ。
    この回答のコメントで続けたとして後の人のためにはならないからです。
    つまり質問本文に追記→その質問を受け取った回答者が回答に追記(または新しい回答を投稿)という流れが望ましいです。

    「ファイルサイズに制限をかけて、画像を選びなおすようにエラー表示させたいです。」と
    「例外処理メッセージを「送信完了しました。」が表示されるタイミングで表示させたい」
    はタイミングが交差しています。
    選びなおさせたいのであれば「完了させました」を出すべきではないですし、「確認画面」すら表示させるのもおかしいです。
    確認画面は「入力を受け付けたうえで入力内容が送りたい内容かユーザーに確認させる画面」ですので。

    ほかについては質問に追記されてから私の回答に追記します。

    キャンセル

  • 2019/05/11 14:35

    そうですね...。
    一度整理して、質問を投稿し直そうと思います。
    お忙しいのにありがとうございました。

    キャンセル

  • 2019/05/11 14:39

    回答に追記はしておくので、そちらを読んでからのほうがいいです。
    この質問、まだきちんと締まってないので。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.52%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる