Ich dachte mir, ich würde eine kurze (für mich ist das kurz) "Antwort" schreiben, nur damit ich meine Punkte zusammenfassen könnte.
Einige "Best Practices" beim Erstellen eines Dateispeichersystems. Dateispeicherung ist eine breite Kategorie, daher kann Ihre Laufleistung für einige davon variieren. Nehmen Sie sie einfach als Vorschlag dessen, was meiner Meinung nach gut funktioniert.
Dateinamen Speichern Sie die Datei nicht unter dem Namen, den ihr der Endbenutzer gegeben hat. Sie können und werden alle möglichen beschissenen Charaktere benutzen, die dir das Leben schwer machen. Einige können so schlimm sein wie '
einfache Anführungszeichen, die es unter Linux im Grunde unmöglich machen, die Datei zu lesen oder sogar (direkt) zu löschen. Manche Dinge können einfach erscheinen wie ein Leerzeichen, aber abhängig davon, wo Sie es verwenden und das Betriebssystem auf Ihrem Server, könnten Sie mit
one%20two.txt
enden oder one+two.txt
oder one two.txt
was zu allen möglichen Problemen in Ihren Links führen kann oder auch nicht.
Am besten erstellen Sie einen Hash, so etwas wie sha1
das kann so einfach sein wie {user_id}{orgianl_name}
Der Benutzername verringert die Wahrscheinlichkeit von Kollisionen mit Dateinamen anderer Benutzer.
Ich mache lieber file_hash('sha1', $contents)
Auf diese Weise können Sie das abfangen, wenn jemand dieselbe Datei mehrmals hochlädt (der Inhalt ist derselbe, der Hash ist derselbe). Wenn Sie jedoch erwarten, große Dateien zu haben, sollten Sie Benchmarking durchführen, um zu sehen, welche Art von Leistung sie hat. Ich handhabe meistens kleine Dateien, also funktioniert das gut. - Beachten Sie - dass die Datei mit dem Zeitstempel immer noch gespeichert werden kann, weil der vollständige Name anders ist, aber es macht es ziemlich einfach zu sehen, und es kann in der Datenbank verifiziert werden.
Unabhängig davon, was Sie tun, würde ich ihm einen Zeitstempel time().'-'.$filename
voranstellen . Dies ist eine nützliche Information, da es sich um die absolute Zeit handelt, zu der die Datei erstellt wurde.
Wie für den Namen, den ein Benutzer der Datei gibt. Speichern Sie das einfach im Datenbankeintrag. Auf diese Weise können Sie ihnen den erwarteten Namen zeigen, aber verwenden Sie einen Namen, von dem Sie wissen, dass er immer sicher für Links ist.
$filename ='etwas beschissenes^ 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";
Ausgänge
Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg
Sie können es hier ausprobieren
Pfade Speichern Sie niemals den vollständigen Pfad zur Datei. Alles, was Sie in der Datenbank benötigen, ist der Hash aus der Erstellung des Hash-Namens. Der "Root"-Pfad zu dem Ordner, in dem die Datei gespeichert ist, sollte in PHP erfolgen. Dies hat mehrere Vorteile.
- verhindert Verzeichnisübertragung. Da Sie keinen Teil des Weges umrunden, müssen Sie sich nicht so viele Sorgen machen, dass jemandem ein
\..\..
zurutscht dort drin und gehen Orte, die sie nicht sollten. Ein schlechtes Beispiel hierfür wäre jemand, der ein.htpassword
überschreibt Datei, indem Sie eine Datei namens that mit dem darin enthaltenen Verzeichnis transversal hochladen. - Hat einheitlicher aussehende Links, einheitliche Größe, einheitlichen Zeichensatz.
https://en.wikipedia.org/wiki/Directory_traversal_attack
- Wartung. Pfade ändern sich, Server ändern sich. Anforderungen an Ihr System ändern sich. Wenn Sie diese Dateien verschieben müssen, aber den absoluten vollständigen Pfad zu ihnen in der DB gespeichert haben, kleben Sie alles mit
symlinks
zusammen oder alle Ihre Datensätze aktualisieren.
Hiervon gibt es einige Ausnahmen. Wenn Sie sie in einem monatlichen Ordner oder nach Benutzernamen speichern möchten. Sie können diesen Teil des Pfads in einem separaten Feld speichern. Aber selbst in diesem Fall könnten Sie es basierend auf den im Datensatz gespeicherten Daten dynamisch erstellen. Ich habe festgestellt, dass es am besten ist, so wenig Pfadinformationen wie möglich zu speichern. Und sie erstellen eine Konfiguration oder eine Konstante, die Sie an allen Stellen verwenden können, an denen Sie den Pfad zur Datei angeben müssen.
Auch der path
und den link
sind sehr unterschiedlich, so dass Sie, indem Sie nur den Namen speichern, ihn von jeder gewünschten PHP-Seite aus verlinken können, ohne Daten vom Pfad abziehen zu müssen. Ich fand es immer einfacher, zum Dateinamen hinzuzufügen, als von einem Pfad abzuziehen.
Datenbank (Nur einige Vorschläge, Verwendung kann variieren) Wie immer bei Daten fragen Sie sich, wer, was, wo, wann
- id -
int
Automatisches Inkrement des Primärschlüssels - user_id -
int
Fremdschlüssel, wer hochgeladen - Hash -
char[40] *sha1*, unique
was der Hash - Hashname -
varchar
{timestampl}-{hash}.{ext} wo den Dateinamen auf der Festplatte - Dateiname -
varchar
der ursprüngliche Name, den der Benutzer angegeben hat, damit wir ihm den erwarteten Namen zeigen können ( falls das wichtig ist ) - Status -
enum[public,private,deleted,pending.. etc]
Status der Datei, je nach Anwendungsfall müssen Sie die Dateien möglicherweise überprüfen, oder einige sind privat, nur der Benutzer kann sie sehen, andere sind möglicherweise öffentlich usw. - Statusdatum -
timestamp|datetime
wann der Status geändert wurde. - create_date -
timestamp|datetime
wann Wenn die Datei erstellt wurde, wird ein Zeitstempel bevorzugt, da er einige Dinge vereinfacht, aber in diesem Fall sollte derselbe Zeitstempel im Hashnamen verwendet werden. - tippen -
varchar
- MIME-Typ, kann nützlich sein, um den MIME-Typ beim Herunterladen usw. einzustellen.
Wenn Sie davon ausgehen, dass verschiedene Benutzer dieselbe Datei hochladen, und Sie den file_hash
verwenden Sie können den hash
erstellen Feld einen kombinierten eindeutigen Index der user_id
und der hash
Auf diese Weise würde es nur zu Konflikten kommen, wenn derselbe Benutzer dieselbe Datei hochgeladen hat. Sie können dies auch basierend auf dem Zeitstempel und dem Hash tun, je nach Ihren Anforderungen.
Das sind die grundlegenden Dinge, an die ich denken könnte, dies ist nicht absolut, nur einige Felder, von denen ich dachte, dass sie nützlich wären.
Es ist nützlich, den Hash allein zu haben, wenn Sie ihn selbst speichern, können Sie ihn in einem CHAR(40)
speichern für sha1 (nimmt weniger Platz in der DB ein als VARCHAR
) und setzen Sie die Sortierung auf UTF8_bin
was binär ist. Dadurch wird bei der Suche darauf zwischen Groß- und Kleinschreibung unterschieden. Obwohl die Wahrscheinlichkeit einer Hash-Kollision gering ist, fügt dies nur ein wenig mehr Schutz hinzu, da Hashes aus Groß- und Kleinbuchstaben bestehen.
Sie können den hashname
immer erstellen on the fly, wenn Sie die Erweiterung und den Zeitstempel getrennt speichern. Wenn Sie immer wieder Dinge erstellen, möchten Sie diese vielleicht einfach in der DB speichern, um die Arbeit in PHP zu vereinfachen.
Ich mag es, einfach den Hash in den Link zu setzen, keine Erweiterung, nichts, also sehen meine Links so aus.
http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea
Echt einfach, echt generisch, sicher in URLs immer gleich groß etc..
Der hashname
für diese "Datei" würde so aussehen
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg
Wenn Sie Konflikte mit derselben Datei und einem anderen Benutzer haben (was ich oben erwähnt habe). Sie können dem Link jederzeit den Zeitstempelteil, die user_id oder beides hinzufügen. Wenn Sie die user_id verwenden, kann es nützlich sein, sie links mit Nullen aufzufüllen. Einige Benutzer können beispielsweise ID:1
haben und einige können ID:234
sein Sie könnten es also auf 4 Stellen auffüllen und zu 0001
machen und 0234
. Dann fügen Sie das dem Hash hinzu, was fast unbemerkt ist:
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg
Das Wichtigste hier ist, dass sha1
ist immer 40
und die ID ist immer 4
wir können die beiden genau und einfach trennen. Und auf diese Weise können Sie es immer noch eindeutig nachschlagen. Es gibt viele verschiedene Optionen, aber so viel hängt von Ihren Bedürfnissen ab.
Zugriff Wie zum Beispiel das Herunterladen. Sie sollten die Datei immer mit PHP ausgeben, geben Sie ihnen keinen direkten Zugriff auf die Datei. Am besten speichern Sie die Dateien außerhalb des Webroots (über dem public_html
, oder www
Mappe ). Dann können Sie in PHP die Header auf den richtigen Typ setzen und die Datei im Grunde auslesen. Dies funktioniert für so ziemlich alles außer Video. Ich handhabe keine Videos, also ist das ein Thema außerhalb meiner Erfahrung. Aber ich denke, es ist am besten, sich vorzustellen, dass alle Dateidaten Text sind, es sind die Kopfzeilen, die diesen Text in ein Bild oder eine Excel-Datei oder ein PDF verwandeln.
Der große Vorteil, ihnen keinen direkten Zugriff auf die Datei zu gewähren, besteht darin, dass Sie, wenn Sie eine Mitgliederseite haben und nicht möchten, dass Ihre Inhalte ohne Anmeldung zugänglich sind, einfach in PHP überprüfen können, ob sie angemeldet sind, bevor Sie ihnen den Inhalt geben. Und da sich die Datei außerhalb des Webroots befindet, können sie nicht auf andere Weise darauf zugreifen.
Das Wichtigste ist, etwas Konsistentes auszuwählen, das dennoch flexibel genug ist, um alle Ihre Anforderungen zu erfüllen.
Ich bin mir sicher, dass mir noch mehr einfallen wird, aber wenn Sie irgendwelche Vorschläge haben, zögern Sie nicht zu kommentieren.
BASISPROZESSABLAUF
- Benutzer übermittelt Formular (
enctype="multipart/form-data"
)
https://www.w3schools.com/tags/att_form_enctype.asp
- Der Server empfängt die Post aus dem Formular Super Globals
$_POST
und die$_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)
]
];
-
Suchen Sie nach Fehlern
if(!$_FILES['fielname']['error'])
-
Anzeigenamen bereinigen
$filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");
-
Datei speichern, DB-Record erstellen ( PSUDO-CODE )
So:
$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 -hochgeladene-Datei.php
-
Erstellen Sie einen Link (variiert je nach Routing), der einfache Weg ist, Ihren Link so zu erstellen
http://www.example.com/download?file={$hash}
aber es ist hässlicher alshttp://www.example.com/download/{$hash}
-
Benutzer klickt auf den Link, um zur Download-Seite zu gelangen.
Holen Sie sich INPUT und suchen Sie den Datensatz
$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
usw....
Prost!