私は自分のポイントを要約できるように、短い(私にとってはこれは短い)「答え」を書くと思いました。
ファイルストレージシステムを作成する際のいくつかの「ベストプラクティス」。ファイルストレージは幅広いカテゴリであるため、これらの一部ではマイレージが異なる場合があります。私が見つけたものの提案がうまくいくのと同じようにそれらを取りなさい。
ファイル名 エンドユーザーが付けた名前でファイルを保存しないでください。彼らはあなたの人生を惨めにするあらゆる種類のくだらないキャラクターを使うことができ、そして使うでしょう。 '
と同じくらい悪いものもあります 一重引用符。Linuxでは基本的にそれが可能であるため、ファイルを読み取ることも、ファイルを削除することもできません(直接)。 のように単純に見えるものもあります スペースですが、使用する場所とサーバー上のOSによっては、
one%20two.txt
になってしまう可能性があります。 またはone+two.txt
またはone two.txt
リンクにあらゆる種類の問題が発生する場合と発生しない場合があります。
最善の方法は、sha1
のようなハッシュを作成することです。 これは、{user_id}{orgianl_name}
と同じくらい簡単です。 ユーザー名により、他のユーザーのファイル名との衝突の可能性が低くなります。
file_hash('sha1', $contents)
を実行することをお勧めします そうすれば、誰かが同じファイルをアップロードした場合、それをキャッチできれば(内容は同じで、ハッシュも同じです)。ただし、大きなファイルがあると予想される場合は、そのファイルにベンチマークを実行して、パフォーマンスの種類を確認することをお勧めします。私は主に小さなファイルを処理するので、それで問題なく動作します。-注-タイムスタンプを使用すると、フルネームが異なるためファイルを保存できますが、非常に見やすく、データベースで確認できます。
何をするかに関係なく、タイムスタンプtime().'-'.$filename
をプレフィックスとして付けます。 。これは、ファイルが作成された絶対時間であるため、持っておくと便利な情報です。
名前については、ユーザーがファイルに付けます。それをデータベースレコードに保存するだけです。このようにして、彼らが期待する名前を示すことができますが、リンクに対して常に安全であることがわかっている名前を使用してください。
$ filename='いくつかのくだらない^fileane.jpg';
$ext = strrchr($filename, '.');
echo "\nExt: {$ext}\n";
$hash = sha1('some crapy^ fileane.jpg');
echo "Hash: {$hash}\n";
$time = time();
echo "Timestamp: {$time}\n";
$hashname = $time.'-'.$hash.$ext;
echo "Hashname: $hashname\n";
Ouputs
Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg
こちら でお試しいただけます
パス ファイルへのフルパスは絶対に保存しないでください。データベースに必要なのは、ハッシュ名を作成したときのハッシュだけです。ファイルが保存されているフォルダへの「ルート」パスは、PHPで実行する必要があります。これにはいくつかの利点があります。
- ディレクトリ転送を防止します。パスのどの部分も通過しないので、誰かが
\..\..
を滑らせることをそれほど心配する必要はありません。 そこに行ってはいけない場所に行きます。この悪い例は、誰かが.htpassword
を上書きすることです。 ディレクトリを横切るという名前のファイルをアップロードしてファイルします。 - より均一に見えるリンク、均一なサイズ、均一な文字セットを備えています。
https://en.wikipedia.org/wiki/Directory_traversal_attack
- メンテナンス。パスが変更され、サーバーが変更されます。システムへの要求は変化します。これらのファイルを再配置する必要があるが、それらへの絶対フルパスをDBに保存した場合は、すべてを
symlinks
で接着することに固執します。 またはすべてのレコードを更新します。
これにはいくつかの例外があります。月次フォルダまたはユーザー名で保存する場合。パスのその部分を別のフィールドに保存できます。ただし、その場合でも、レコードに保存されているデータに基づいて動的に構築できます。パス情報をできるだけ少なく保存するのが最善であることがわかりました。そして、それらは、ファイルへのパスを配置するために必要なすべての場所で使用できる構成または定数を作成します。
また、path
およびlink
は非常に異なるため、名前のみを保存することで、パスからデータを差し引くことなく、必要なPHPページから名前をリンクできます。ファイル名に追加してからパスから減算する方が簡単だといつも思っています。
データベース (いくつかの提案ですが、使用方法は異なる場合があります)データの場合と同様に、誰が、何を、どこで、いつ行うかを自問してください。
- id -
int
主キーの自動インクリメント - user_id -
int
外部キー、誰 アップロードしました - ハッシュ -
char[40] *sha1*, unique
何 ハッシュ - ハッシュ名 -
varchar
{timestampl}-{hash}。{ext}where ハードドライブ上のファイル名 - ファイル名 -
varchar
ユーザーが付けた元の名前。そうすれば、ユーザーが期待する名前を表示できます(それが重要な場合) - ステータス -
enum[public,private,deleted,pending.. etc]
ファイルのステータスは、ユースケースによっては、ファイルを確認する必要がある場合もあれば、ユーザーだけが見ることができるプライベートなものもあれば、パブリックなものもあります。 - status_date -
timestamp|datetime
ステータスが変更された時刻。 - create_date -
timestamp|datetime
いつ ファイルが作成されたときは、タイムスタンプが好まれます。これにより、いくつかのことが簡単になりますが、その場合は、ハッシュ名で使用されるタイムスタンプと同じである必要があります。 - タイプ -
varchar
--mimeタイプ。ダウンロード時などにmimeタイプを設定するのに便利です。
異なるユーザーが同じファイルをアップロードすることを期待し、file_hash
を使用する場合 hash
を作成できます user_id
の結合された一意のインデックスをフィールドします およびhash
このようにすると、同じユーザーが同じファイルをアップロードした場合にのみ競合します。必要に応じて、タイムスタンプとハッシュに基づいて実行することもできます。
それは私が考えることができる基本的なことです、これは私が役に立つと思ったいくつかの分野だけで絶対的なものではありません。
ハッシュを単独で保持すると便利です。ハッシュを単独で格納する場合は、CHAR(40)
に格納できます。 sha1の場合(DBで使用するスペースがVARCHAR
よりも少なくなります )そして照合をUTF8_bin
に設定します これはバイナリです。これにより、大文字と小文字が区別されます。ハッシュの衝突の可能性はほとんどありませんが、ハッシュは大文字と小文字であるため、これにより保護が少し強化されます。
hashname
はいつでも作成できます 拡張子とタイムスタンプを別々に保存すると、その場で。何度も作成していることに気付いた場合は、PHPでの作業を簡素化するために、それをDBに保存することをお勧めします。
リンクにハッシュを入れるのが好きです。拡張子は何もないので、リンクは次のようになります。
http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea
本当にシンプルで、本当に一般的で、URLは常に同じサイズなどで安全です。
hashname
この「ファイル」は次のようになります
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg
同じファイルと異なるユーザー(前述)との競合がある場合。タイムスタンプ部分は、リンク、user_id、またはその両方にいつでも追加できます。 user_idを使用する場合は、ゼロを左に埋めると便利な場合があります。たとえば、一部のユーザーはID:1
を持っている場合があります ID:234
の場合もあります したがって、4つの場所にパッドを残して、0001
にすることができます。 および0234
。次に、それをハッシュに追加します。これはほとんど目立たないものです:
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg
ここで重要なのは、sha1
常に40
です IDは常に4
2つを正確かつ簡単に分離できます。そしてこのように、あなたはまだそれをユニークに調べることができます。さまざまなオプションがありますが、ニーズによって異なります。
アクセス ダウンロードなど。常にPHPでファイルを出力する必要があり、ファイルへの直接アクセスを許可しないでください。最善の方法は、ファイルをWebルートの外部(public_html
の上)に保存することです。 、またはwww
フォルダ)。次に、PHPでは、ヘッダーを正しいタイプに設定し、基本的にファイルを読み取ることができます。これは、ビデオを除くほとんどすべての場合に機能します。私はビデオを扱っていないので、それは私の経験以外のトピックです。しかし、すべてのファイルデータはテキストであり、そのテキストを画像にするヘッダー、またはExcelファイルやPDFであるため、これを考えるのが最善だと思います。
ファイルへの直接アクセスを許可しないことの大きな利点は、メンバーシップサイトがある場合、ログインせずにコンテンツにアクセスしたくない場合、コンテンツを提供する前に、ログインしているかどうかをPHPで簡単に確認できることです。また、ファイルはWebルートの外部にあるため、他の方法でアクセスすることはできません。
最も重要なことは、一貫性のあるものを選択することです。それでも、すべてのニーズを処理するのに十分な柔軟性があります。
もっと思いつくことができると思いますが、何か提案があれば、遠慮なくコメントしてください。
基本的なプロセスフロー
- ユーザーがフォームを送信します(
enctype="multipart/form-data"
)
https://www.w3schools.com/tags/att_form_enctype.asp
- サーバーは、Super Globals
$_POST
のフォームから投稿を受け取ります および$_FILES
http://php.net/manual/en/reserved.variables.files .php
$_FILES = [
'fieldname' => [
'name' => "MyFile.txt" // (comes from the browser, so treat as tainted)
'type' => "text/plain" // (not sure where it gets this from - assume the browser, so treat as tainted)
'tmp_name' => "/tmp/php/php1h4j1o" // (could be anywhere on your system, depending on your config settings, but the user has no control, so this isn't tainted)
'error' => "0" //UPLOAD_ERR_OK (= 0)
'size' => "123" // (the size in bytes)
]
];
-
エラーをチェックします
if(!$_FILES['fielname']['error'])
-
表示名をサニタイズする
$filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");
-
ファイルを保存し、DBレコードを作成します(PSUDO-CODE)
このように:
$path = __DIR__.'/uploads/'; //for exmaple
$time = time();
$hash = hash_file('sha1',$_FILES['fielname']['tmp_name']);
$type = $_FILES['fielname']['type'];
$hashname = $time.'-'.$hash.strrchr($_FILES['fielname']['name'], '.');
$status = 'pending';
if(!move_uploaded_file ($_FILES['fielname']['tmp_name'], $path.$hashname )){
//failed
//do somehing for errors.
die();
}
//store record in db
http://php.net/manual/en/function.move -uploaded-file.php
-
リンクを作成します(ルーティングによって異なります)。簡単な方法は、次のようにリンクを作成することです。
http://www.example.com/download?file={$hash}
しかし、それは醜いですhttp://www.example.com/download/{$hash}
-
ユーザーがリンクをクリックすると、ダウンロードページに移動します。
INPUTを取得し、レコードを検索します
$hash = $_GET['file'];
$stmt = $PDO->prepare("SELECT * FROM attachments WHERE hash = :hash LIMIT 1");
$stmt->execute([":hash" => $hash]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
http://php.net/manual/en/intro.pdo.php
など...
乾杯!