プチ・クラウドストレージ作ってみた

やりたいこと
**プチ・クラウドストレージ**
・ファイルをどこからでもアップ、保管してダウンロードもできる。
・セキュリティも兼ねてIDとパスワードでログイン形式にする。<br>
まずはパワーポイントでサイトの系図を設計しました。

![siteroot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641189/9d041035-a6ed-c710-f04a-691efb1f3c95.jpeg)

次は手順を考えてイメージを具体化。
これは無料版のAdobeのXDを使ってみました。

操作も簡単で、感覚的に作れちゃうので便利です。
ページ自体はシンプルにしたかったのでフォントだけで作りました。
- - -
### ログインページ
AdobeXD イメージ図
![Top.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641189/15709a44-04df-a7ba-c68c-d3e42417eead.jpeg)
パスワードとIDはここで決めてます。
空文字NGの条件も設定。

```php:login.php
//XSS
function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}
//トークン生成
function getCSRFToken()
{
    $nonce = base64_encode(openssl_random_pseudo_bytes(48));
    setcookie('XSRF-TOKEN', $nonce);

    return $nonce;
}
$token = getCSRFToken();
$token = html_escape($token);
//formにhidden追加

$logid = '';
$pass = '';
$messege = '';

if($_SERVER['REQUEST_METHOD'] === 'POST'){
    //isset入れると空文字条件が効かない
    $logid = $_POST['logid'];
    $pass = $_POST['pass'];
    $logid = html_escape($logid);
    $pass = html_escape($pass);

    //postトークン追加
        function validateCSRFToken ($post_token)
        {
            return isset($_COOKIE['XSRF-TOKEN']) && $_COOKIE['XSRF-TOKEN'] === $post_token;
        }
        if(isset($_POST['csrf_token']) && validateCSRFToken($_POST['csrf_token'])){
            //OKだったら空文字でスルー
            echo '';
        } else {
            echo 'トークンが不正です。';
            exit();
        }

        header('Access-Control-Allow-Origin: path');
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('X-Frame-Options: SAMEORIGIN');
    //postトークンここまで
    
    //IDとパスワード設定
    if($logid === 'keserasera' && $pass === 'keserasera'){
        session_start();
        $_SESSION['login'] = 1;
        //ファイル一覧へリロード
        header('Location: file_list.php');
        exit();
    } elseif ($logid === '' || $pass === ''){
        $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDとパスワードを空文字にしないで入力してください</p>';
    } else {
        $messege = '<p class="notice"><i class="fas fa-info-circle"></i>IDかパスワード、もしくは両方違います</p>';
    }
}

?>
<!doctype html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>ファイル預かり処・マイ保管庫</title>
   <!-- headerインクルード -->
    <?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処　マイ保管庫</h1>
          <p class="tx14">ログインページ</p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    <main id="topMain">
      <form action="" method="post" class="clearfix">
         <!-- トークン追加 -->
         <input type="hidden" name="csrf_token" value="<?php echo $token ?>">
         <label>ログインID</label>
         <input type="text" name="logid">
         <label>パスワード</label>
         <input type="password" name="pass">
         <p id="logBtn"><input type="submit" value="ログイン"></p>
         <?php echo $messege; ?>
      </form>      
    </main>
    <!-- footerインクルード -->
    <?php require_once(dirname(__FILE__).'/footer.php'); ?>
```

- - -
### ファイルリスト一覧ページ
AdobeXD イメージ図
![file_list.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641189/ac77c108-82f5-f609-11c9-f9c32394634e.jpeg)

アップロードとダウンロード、削除ファイルの3つのformがあります。

- アップロードは同一ページで処理
- ダウンロードはチェックページに飛ばしてリロード処理
- 削除ファイルは確認ページを別に作ってpostデータを渡す

ちなみにダウンロードと削除ファイル一覧リストはタブ切替。
フォルダーの中身は一緒でアップすると自動でリストが増えて、削除すると減っていきます。

```php:file_list.php
//ログインしていないとアクセスさせない
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
        return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
    }
//トークン生成
function getCSRFToken()
{
    $nonce = base64_encode(openssl_random_pseudo_bytes(48));
    setcookie('XSRF-TOKEN', $nonce);

    return $nonce;
}
$token = getCSRFToken();
$token = html_escape($token);
//formにhidden追加

$up_file = '';
$messege = '';
$select_file = '<p id="take">ファイルを選択して下さい</p>';
$restore = '';
$up_before = 'upBefore';//非表示css
$filename = '';

if($_SERVER['REQUEST_METHOD'] === 'POST'){
        //postトークン追加
        function validateCSRFToken ($post_token)
        {
            return isset($_COOKIE['XSRF-TOKEN']) && $_COOKIE['XSRF-TOKEN'] === $post_token;
        }
        if(isset($_POST['csrf_token']) && validateCSRFToken($_POST['csrf_token'])){
            //OKだったら空文字でスルー
             echo '';
        } else {
            echo 'トークンが不正です。';
            exit();
        }

        header('Access-Control-Allow-Origin: path');
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('X-Frame-Options: SAMEORIGIN');
    //postトークンここまで

    $up_file = $_FILES['file_up'];
    $up_file['name'] = html_escape($up_file['name']);
    $up_file['name'] = strtolower($up_file['name']);//英小文字に変換  
    
    $extension = Pathinfo($up_file['name'],PATHINFO_EXTENSION);//.以降の拡張子を重複しないよう整形
    $filename = Pathinfo($up_file['name'],PATHINFO_FILENAME);
    $filename = str_replace(['.',' ','　'], '', $filename);//ファイル名に.と半角全角の空白があったら除去

    //base64でencode追加
    $filename = base64_encode($filename);
    //var_dump($filename);
    //デコード
    $filename = base64_decode($filename);
    //var_dump($filename);
    
    //ファイルがNGの場合条件処理
    if($up_file['size'] > 30000000 ){
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ファイルサイズが容量を超えています</p>';
        $restore = '<a id="restore" href="file_list.php">こちらからUPし直してください</a>';
        //phpよりエラー表示を出すときファイル選択ボタンを出さない為、空文字設定に
        $select_file = '';
    } elseif(file_exists('../../../up_file/'.$filename.'.'.$extension) === TRUE) {
        //file_exists関数でディレクトリ内を調べて、同じファイル名があった場合はアップさせない
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>同名のファイルがあります。違うファイル名にしてください。</p>';       
    } elseif(file_exists('../../../up_file') === FALSE) {
        //ディレクトリーが無かった場合の処理
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ディレクトリーが見当たりません。ファイルパスを確認するかもしくは新規で作成してください。</p>';
    } else {
        $messege = '';
        $up_before = 'upAfter';
        
        //同名ファイルを連番で上げる場合、file_exists()の同名ファイル条件を削除
        /*$suffix = 0;
        while(file_exists('../../../up_file/'. $filename. ($suffix ? sprintf('_%d', $suffix) : ''). "." . $extension)) $suffix++;
       move_uploaded_file($up_file['tmp_name'],'../../../up_file/'.$filename.($suffix ? sprintf('_%d', $suffix) : '').'.'.$extension);*/
       
       //fileをアップする関数
        move_uploaded_file($up_file['tmp_name'],'../../../up_file/'.$filename.'.'.$extension);     
    }
}

   //upフォルダの中身
    $dw_items =  glob('../../../up_file/*');//DL用、同じでも変数変えないとエラーになる
    $del_items = glob('../../../up_file/*');//削除用、grobは配列形式でファイルパスを取得
?>
<!doctype html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>ファイル一覧 | ファイル預かり処・マイ保管庫</title>	
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処　マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    <main id="main">
        <h2 class="pageTitle">ファイルアップロード</h2>
     <!-- ファイルup -->
        <form id="upBox" action="" method="post" enctype="multipart/form-data">
              <!-- トークン追加 -->
              <input type="hidden" name="csrf_token" value="<?php echo $token ?>">
              <p class="notice">※ファイルsizeは1つにつき30MBまで、名前は英小文字で</p>
              <!-- UPし直し表示 -->
              <?php echo $restore; ?>
              <p id="<?php echo $up_before; ?>">ファイル「<?php echo $filename; ?>」はアップされました。続けてUPできます</p>
              <label for="up">
                  <!-- ファイルを選択ボタン -->
                  <?php echo $select_file; ?>
              </label>
              <input id="up" type="file" name="file_up">
              <p id="send"><input type="submit" value=""></p>
              <!-- エラーメッセージ -->
              <?php echo $messege; ?>
        </form>
      
      <div id="fileBox">
          <p id="fileCount">ファイル数<?php echo count($dw_items); ?>項目</p>         
          <h3 class="selectFile" id="dowTab">DL用ファイル一覧</h3>  
          <form id="dowList" action="download_file.php" method="post">
              <!-- トークン追加 -->
              <input type="hidden" name="csrf_token" value="<?php echo $token ?>">
             <!-- 飛び先でLocation　リダイレクト処理-->
              <ul>
                 <?php if(count($dw_items) === 0): ?>
                    <li>まだファイルはありません</li>
                    <?php else: ?>
                      <?php foreach($dw_items as $items): $dw_name = Pathinfo($items,PATHINFO_BASENAME); ?>                   
                     <li><input type="radio" name="dowl" value="<?php echo $dw_name; ?>"><?php echo $dw_name; ?></li>                     
                      <?php endforeach; ?>
                  <?php endif; ?>
              </ul>
              <p class="notice">※左のラジオボタンにチェックしてダウンロードボタンをクリックしてください</p>
              <p class="pibtn"><input type="submit" value="Download"></p>
          </form>
                   
          <h3 class="selectFile" id="delTab">削除用ファイル一覧</h3>
          <form id="delList" action="delete_confilm.php" method="post">
              <!-- トークン追加 -->
              <input type="hidden" name="csrf_token" value="<?php echo $token ?>">
              <ul>
                 <?php if(count($dw_items) === 0): ?>
                    <li>まだファイルはありません</li>
                    <?php else: ?>
                      <?php foreach($del_items as $items): $del_name = Pathinfo($items,PATHINFO_BASENAME); ?>
                         <li><input type="checkbox" name="del[]" value="<?php echo $del_name; ?>"><?php echo $del_name; ?></li>  
                      <?php endforeach; ?>
                  <?php endif; ?>
              </ul>
              <p class="notice">※左のチェックボックスを選択(複数可)して確認ボタンをクリックしてください</p>
              <p class="vibtn"><input type="submit" value="削除確認"></p>
          </form>
      </div><!-- //id="fileBox"-->
               
    </main>  
<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>

```
<br>
input type="file"は特殊で、デフォルトのボタンを使うのはビジュアル面で抵抗があったのでカスタマイズしました。<br>
ファイルが選択されたらボタンがファイル名に変わって、エラーだったら上にメッセージ表示。
OKだったらtype="file"ブロックは非表示になって、type="submit"にすり替え。
見た目は一緒のボタンです。<br>
ファイルアップが成功したらボタンの上にファイル名が表示して下のリストに追加といった仕様。<br>
ただ、ファイル選択時はsubmitボタンは押されてないのでphpでの処理が難しい。
そこでjsのchangeイベントを活用。
ここは頭がこんがらがりました。phpとjsとcssのトリプル連携。

```js:submit_btn.js
jQuery('#up').on('change',function(){
      const upfile = jQuery('#up').get(0).files;
      console.log(upfile);
      //多次元配列になるのかな? upfile[[name: xxx,size: xxx]]
      const fileName = upfile[0].name;            
      console.log(fileName);
       //file選択をsubmitにすり替え
      jQuery('#take').css('display','none');
      jQuery('#send').css('display','block');
      jQuery('#send input').val(fileName);
   });

```

---
### ダウンロードチェックページ
ダウンロードのコードはどうしても分からなかったのでググって動いたものをコピペさせてもらいました。
それまではダウンロードできても開けられなかったり不具合続出。<br>
ここのコードが一番難しい。
今の段階では理解できなかったのですが、そのうち自分でも組めるようになりたいです。

[参考サイトはこちら](https://qiita.com/fallout/items/3682e529d189693109eb)

```php:download_file.php
//ダウンロードチェック
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}
//postトークン追加
function validateCSRFToken ($post_token)
{
    return isset($_COOKIE['XSRF-TOKEN']) && $_COOKIE['XSRF-TOKEN'] === $post_token;
}

if(isset($_POST['csrf_token']) && validateCSRFToken($_POST['csrf_token'])){
    echo '';
} else {
    echo 'トークンが不正です。';
    exit();
}

header('Access-Control-Allow-Origin: path');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('X-Frame-Options: SAMEORIGIN');
//postトークンここまで

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
        return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
    }
$dowload_file = $_POST['dowl'];
$dowload_file = html_escape($dowload_file);

//var_dump($dowload_file);

//ここのコード分からなかったのでネットからググって拾ってきた
    function download($pPath, $pMimeType = null){
    //-- ファイルが読めない時はエラー(もっときちんと書いた方が良いが今回は割愛)
    if (!is_readable($pPath)) { die($pPath); }

    //-- Content-Typeとして送信するMIMEタイプ(第2引数を渡さない場合は自動判定) ※詳細は後述
    $mimeType = (isset($pMimeType)) ? $pMimeType
                                    : (new finfo(FILEINFO_MIME_TYPE))->file($pPath);

    //-- 適切なMIMEタイプが得られない時は、未知のファイルを示すapplication/octet-streamとする
    if (!preg_match('/\A\S+?\/\S+/', $mimeType)) {
        $mimeType = 'application/octet-stream';
    }

    //-- Content-Type
    header('Content-Type: ' . $mimeType);

    //-- ウェブブラウザが独自にMIMEタイプを判断する処理を抑止する
    header('X-Content-Type-Options: nosniff');

    //-- ダウンロードファイルのサイズ
    header('Content-Length: ' . filesize($pPath));

    //-- ダウンロード時のファイル名
    header('Content-Disposition: attachment; filename="' . basename($pPath) . '"');

    //-- keep-aliveを無効にする
    header('Connection: close');

    //-- readfile()の前に出力バッファリングを無効化する ※詳細は後述
    while (ob_get_level()) { ob_end_clean(); }

    //-- 出力
    readfile($pPath);

    //-- 最後に終了させるのを忘れない
    exit;
}

//選択されたファイルがあったらダウンロード、なかったらそのままリダイレクト
//選択されないままダウンロード処理されちゃうと変なファイルがDLされる
if(isset($_POST['dowl'])){
    download('../../../up_file/*'.$dowload_file);
    header('Location: file_list.php');   
} else {
   header('Location: file_list.php');
}

```

---

### 削除確認ページ
AdobeXD イメージ図
![check.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641189/8cc0f8a3-99fa-e90a-6fb9-fc1d0a2588fa.jpeg)

削除だけは誤って消してしまって後悔しないように、確認してからの導線にしました。
ファイルが選択されていない場合は戻るボタンだけにしています。

```php:delete_confilm.php
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

//postトークン追加
function validateCSRFToken ($post_token)
{
    return isset($_COOKIE['XSRF-TOKEN']) && $_COOKIE['XSRF-TOKEN'] === $post_token;
}

if(isset($_POST['csrf_token']) && validateCSRFToken($_POST['csrf_token'])){
    echo '';
} else {
    echo 'トークンが不正です。';
    exit();
}

header('Access-Control-Allow-Origin: path');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('X-Frame-Options: SAMEORIGIN');
//postトークンここまで

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}
//トークン生成
function getCSRFToken()
{
    $nonce = base64_encode(openssl_random_pseudo_bytes(48));
    setcookie('XSRF-TOKEN', $nonce);

    return $nonce;
}
$token = getCSRFToken();
$token = html_escape($token);
//formにhidden追加

$delete_file = '';
$confiText = '';
$btnText = '';
$none = '';

//エラーが出るのでfor文中にエスケープをかけた
if(isset($_POST['del'])){
    for($i = 0; $i < count($_POST['del']); $i++){
        $delete_file .= '<li><input type="hidden" name="check[]" value="'.html_escape($_POST['del'][$i]).'"><i class="far fa-file"></i>'.html_escape($_POST['del'][$i]).'</li>';
    }
    $confiText = '以下のファイルを削除してもいいですか?';
    $btnText = 'キャンセル';
} else {
    $delete_file = '<li><i class="far fa-file"></i>ファイルが選択されていません</li>';
    $confiText = '';
    $btnText = '戻る';
    $none = 'none';
}
?>
<!doctype html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>ファイル削除確認 | ファイル預かり処・マイ保管庫</title>
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処　マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    
    <main id="confiBox">
        <h2 class="pageTitle">ファイル削除確認</h2>
        <p class="confiText"><?php echo $confiText; ?></p>
        <form action="delete_done.php" method="post">
         <!-- トークン追加 -->
         <input type="hidden" name="csrf_token" value="<?php echo $token ?>">
            <ul>
                <?php echo $delete_file; ?>
            </ul>
            <div id="btnBlock">
                <p id="cansel"><a href="file_list.php"><?php echo $btnText; ?></a></p>
                <p id="decision" class="<?php echo $none; ?>"><input type="submit" value="削除"></p>
            </div>
        </form>
    </main>
     
<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>

```

---

### 削除完了ページ
AdobeXD イメージ図
![del_done.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641189/d719b062-67fc-5275-85ed-1b6dd0ecce3a.jpeg)

削除確認ページのレイアウトとほぼ一緒です。

```php:delete_done.php
//ログインしていないとアクセスさせない
session_start();
session_regenerate_id(true);
if(isset($_SESSION['login']) === false){
    header('Location: un_login.php');
    exit();
}

//postトークン追加
function validateCSRFToken ($post_token)
{
    return isset($_COOKIE['XSRF-TOKEN']) && $_COOKIE['XSRF-TOKEN'] === $post_token;
}

if(isset($_POST['csrf_token']) && validateCSRFToken($_POST['csrf_token'])){
    echo '';
} else {
    echo 'トークンが不正です。';
    exit();
}

header('Access-Control-Allow-Origin: path');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('X-Frame-Options: SAMEORIGIN');
//postトークンここまで

//Pathinfoの日本語ファイル名対応処置
setlocale(LC_ALL, "ja_JP.UTF-8");

function html_escape($word){
    return htmlspecialchars($word,ENT_QUOTES,'UTF-8');
}

$delete_file = '';

//POSTで渡されたファイルを削除
if(isset($_POST['check'])){
    for($i = 0; $i < count($_POST['check']); $i++){           
        unlink('../../../up_file/*'.html_escape($_POST['check'][$i]));
        //削除ファイルli書出し
        $delete_file .= '<li><i class="far fa-file"></i>'.html_escape($_POST['check'][$i]).'</li>';
    }    
}

?>
<!doctype html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>ファイル削除完了 | ファイル預かり処・マイ保管庫</title>
<!-- headerインクルード -->
<?php require_once(dirname(__FILE__).'/header.php'); ?>
<div id="wrapper">
    <header>
       <div id="titleVar">
          <h1><i class="fas fa-circle"></i>ファイル預かり処　マイ保管庫</h1>
           <p id="logout"><a href="logout.php">ログアウト</a></p>
       </div>       
       <div id="topvew">My keep folder</div>             
    </header>
    
    <main id="confiBox">
        <h2 class="pageTitle">ファイル削除完了</h2>
        <p class="confiText">以下のファイルを削除しました</p>
        <ul>
            <?php echo $delete_file; ?>         
        </ul>
        <p id="toListpage"><a href="file_list.php">ファイル一覧ページへ</a></p>
    </main>
     
<!-- footerインクルード -->
<?php require_once(dirname(__FILE__).'/footer.php'); ?>
```

---
### ログアウトページ
ログアウトページは、お約束のセッション破棄だけなのでコードは省きます。
レイアウトはログインページと同じにしました。

---
### ひとまず完成

あとは実際のサーバに上げて動作確認。

動いた時は大感動。
あれ?
でもアップできないファイルがある。

色々試して、どうやら日本語名のファイルはアップできないみたい。

XAMPP開発時では日本語のファイル名でも大丈夫だったんですけどね。
そういえば日本語とサーバの相性は良くないと昔から言われてました。

とりあえず注意書きに日本語NGと追加して応急対処。
preg_matchで条件設定した方がいいのかなと思ったりしています。

ひとまず、これで完成として使い込んでみようと思ってます。
制作期間は約1週間、畑仕事と家事の合間にコツコツと作業。
<br>

---
### 追記
@rfc828さんからご意見いただいたので以下の項目を直してみました。

- 削除確認ページのコード追加
- ファイルアップのディレクトリーをwebからアクセス出来ない場所へ移動
- 同名ファイルの上書き防止機能追加
- 日本語名ファイルでもアップロード可能に

丁寧に教えていただき有り難うございます!

修正前の状態では同名ファイルをアップすると上書きされてしまいました。
なので以下のコードをファイルリスト一覧ページの条件文に追加しました。

```php:
elseif(file_exists('../../../up_file/'.$filename.'.'.$extension) === TRUE) {
        //file_exists関数でディレクトリ内を調べて、同じファイル名があった場合はアップさせない
        $messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>同名のファイルがあります。違うファイル名にしてください。</p>';       
    }
```

同じ名前だったらメッセージのみを出してアップ出来ないようにしています。
乱数に変換する方法とかがありますが、自分が使うことを前提にしてるのでファイル名が分からなくなると困るかなと。
また同じファイルがたまると乱雑になるので、いっそのことアップ出来なくする方がベストかなと思いました。

教えてもらったsetlocale(LC_ALL, "ja_JP.UTF-8")をファイル名が記述しているページに全て追記したら日本語名でアップ出来るようになりました。
サーバの中を覗いてもちゃんと日本語で表示されてて、とても感動です!

ただ、日本語名ファイルの入ったフォルダーをMacでzipに圧縮してWindowsで解凍すると、日本語名が文字化けになってしまいます。
この現象を考えると初めから英小文字にしておいた方が無難かもしれません。
でも日本語名でアップ出来たのは嬉しい。

後の不具合は追々修正していこうと思ってます。

ずっと一人で開発していたので、今回のようにコードを見てくれて教えてもらったことは大変有り難く感謝です!
これからもご意見いただけると嬉しいので、どうぞ宜しくお願いします!

---

### 追加修正しました

- ファイル名の余白を削除するようにしました
- 保存先のディレクトリーが無かったらUP出来ない表示に修正しました
- 連番で同名ファイルがUP出来る方法を教えてもらったのでコメントアウトで追記しました

今まで「・」だけを削除するよう文字整形してましたが(img.png.pngみたいな)半角と全角スペースも削除するよう要素を増やしました。

<strong>ファイルリスト一覧ページ</strong>

旧コード
<code>$filename = str_replace('.', '', $filename);</code>

修正コード
<code>$filename = str_replace(['.',' ','　'], '', $filename);</code>
<br>
保存先のディレクトリーがなくても「UP出来ました」と表記されていたのをエラーメッセージが出るよう条件分岐を追加しました。

追加コード

```php

//ディレクトリーが無かった場合の処理
} elseif(file_exists('../../../up_file') === FALSE) {
$messege = '<p id="caution" class="notice"><i class="fas fa-info-circle"></i>ディレクトリーが見当たりません。ファイルパスを確認するかもしくは新規で作成してください。</p>';
}
```
<br>
連番でファイルアップが出来る方法は@rfc828さんに教えていただきました。
今回はファイルを増やさない方向でいきますが、場合によっては同名ファイルをアップしたいということもあるかと思います。
そんな時は大変重宝します!
ちゃんと動くことも確認しました。
早速コメントアウトしておいて永久保存。

```php
//同名ファイルを連番で上げる場合、上のfile_exists()の同名ファイル条件を削除
$suffix = 0;
 while(file_exists('../../../up_file/'. $filename. ($suffix ? sprintf('_%d', $suffix) : ''). "." . $extension)) $suffix++;

move_uploaded_file($up_file['tmp_name'],'../../../up_file/'.$filename.($suffix ? sprintf('_%d', $suffix) : '').'.'.$extension);

```

本当にありがとうございます!
<br>
ダウンロードチェックページのDL処理について

ダウンロードの関数は初心者には難易度が高かったです。
理解出来てない部分も多々あり、まだまだ知識は浅いです。

@libraplanetさんからアドバイスいただいた、エンコードのことことですが、ブラウザから日本語のURLをコピペするとファイル名が%のついた長い暗号のようなものになっているあの現象のことでしょうか?
調べて見ましたが、rawurlencodeやmb_convert_encodingなのかなと思ってしまいましたが…

教えてもらったにも関わらず、分からないことが多くて修正にまで及びませんでした。
ですがクロスブラウザやOS対応はこれからも考えて行きたいと思っています。
ご意見いただけて本当にありがとうございます。

---

### ワンタイムトークン追加

- formページにトークン生成
- postでデータを受け取るページにトークンチェック
- ディレクトリーに入れる前にbace64で名前のエンコード　

@sengokuさんよりアドバイスをいただいて、自分なりに調べてセキュリティー対策してみました。
全てのformタグの中にトークンをhiddenで入れて値を生成しておく。
postで受ける方は渡された値をチェック。

以下のサイトを参考にさせてもらいました。
[PHP: フォームに CSRF 対策のトークンを導入する　≫](https://blog.sarabande.jp/post/88201025803)<br>
自分のコードの中に入れた時、若干変えた所はありますがほぼコピペです。

```php:php
//トークン生成
function getCSRFToken()
{
    $nonce = base64_encode(openssl_random_pseudo_bytes(48));
    setcookie('XSRF-TOKEN', $nonce);

    return $nonce;
}
$token = getCSRFToken();
$token = html_escape($token);

```

```html:html
<!-- トークン追加 -->
<input type="hidden" name="csrf_token" value="<?php echo $token ?>">
```

```php:php
//トークンチェック
        function validateCSRFToken ($post_token)
        {
            return isset($_COOKIE['XSRF-TOKEN']) && $_COOKIE['XSRF-TOKEN'] === $post_token;
        }
        if(isset($_POST['csrf_token']) && validateCSRFToken($_POST['csrf_token'])){
            //OKだったら空文字でスルー
            echo '';
        } else {
            echo 'トークンが不正です。';
            exit();
        }

        header('Access-Control-Allow-Origin: path');
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('X-Frame-Options: SAMEORIGIN');

```

header関数はLocation以外はあまりわかっていません…

ファイル名エンコードは一旦bace64でエンコードした後、デコードしてからディレクトリーに移動させました。
この状態でいいのかな?と言う感じです。

```php:php
//move_uploaded_file()の前に置きました
    $filename = base64_encode($filename);
    //デコード
    $filename = base64_decode($filename);
```

必要なページに入れてみて動作確認してみました。
今のところ異常なくファイルアップ、削除できてます。

---