J'ai pensé que j'écrirais une "réponse" courte (pour moi, c'est court) juste pour que je puisse résumer mes points.
Quelques "meilleures pratiques" lors de la création d'un système de stockage de fichiers. Le stockage de fichiers est une vaste catégorie, de sorte que votre kilométrage peut varier pour certains d'entre eux. Prenez-les juste comme suggestion de ce que j'ai trouvé qui fonctionne bien.
Noms de fichiers Ne stockez pas le fichier avec le nom qui lui a été donné par un utilisateur final. Ils peuvent utiliser et utiliseront toutes sortes de personnages de merde qui rendront votre vie misérable. Certains peuvent être aussi mauvais que '
les guillemets simples, ce qui, sous Linux, rend impossible la lecture, voire la suppression du fichier (directement). Certaines choses peuvent sembler simples comme un espace mais selon l'endroit où vous l'utilisez et le système d'exploitation de votre serveur, vous pourriez vous retrouver avec
one%20two.txt
ou one+two.txt
ou one two.txt
qui peuvent ou non créer toutes sortes de problèmes dans vos liens.
La meilleure chose à faire est de créer un hachage, quelque chose comme sha1
cela peut être aussi simple que {user_id}{orgianl_name}
Le nom d'utilisateur réduit les risques de collisions avec les noms de fichiers d'autres utilisateurs.
Je préfère faire file_hash('sha1', $contents)
de cette façon, si quelqu'un télécharge le même fichier plus d'une fois, vous pouvez l'attraper (le contenu est le même, le hachage est le même). Mais si vous vous attendez à avoir des fichiers volumineux, vous voudrez peut-être faire des analyses comparatives dessus pour voir quel type de performance il a. Je gère principalement de petits fichiers, donc cela fonctionne bien pour cela. - notez qu'avec l'horodatage, le fichier peut toujours être enregistré car le nom complet est différent, mais cela le rend assez facile à voir et il peut être vérifié dans la base de données.
Indépendamment de ce que vous faites, je le préfixerais avec un horodatage time().'-'.$filename
. C'est une information utile à avoir, car c'est l'heure absolue à laquelle le fichier a été créé.
Quant au nom qu'un utilisateur donne au fichier. Stockez simplement cela dans l'enregistrement de la base de données. De cette façon, vous pouvez leur montrer le nom qu'ils attendent, mais utilisez un nom dont vous savez qu'il est toujours sûr pour les liens.
$filename ='un peu de merde^ 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";
Sorties
Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg
Vous pouvez l'essayer ici
Chemins ne stockez jamais le chemin d'accès complet au fichier. Tout ce dont vous avez besoin dans la base de données est le hachage de la création du nom haché. Le chemin "racine" vers le dossier dans lequel le fichier est stocké doit être fait en PHP. Cela a plusieurs avantages.
- empêche le transfert de répertoire. Parce que vous ne passez aucune partie du chemin autour de vous, vous n'avez pas à vous soucier autant que quelqu'un glisse un
\..\..
là-bas et aller dans des endroits où ils ne devraient pas. Un mauvais exemple de ceci serait quelqu'un écrasant un.htpassword
fichier en téléchargeant un fichier nommé that avec le répertoire transverse dedans. - A des liens plus uniformes, une taille uniforme, un jeu de caractères uniforme.
https://en.wikipedia.org/wiki/Directory_traversal_attack
- Entretien. Les chemins changent, les serveurs changent. Les demandes sur votre changement de système. Si vous avez besoin de déplacer ces fichiers, mais que vous avez stocké le chemin d'accès complet absolu dans la base de données, vous collez tout avec des
symlinks
ou mettre à jour tous vos enregistrements.
Il y a quelques exceptions à cela. Si vous souhaitez les stocker dans un dossier mensuel ou par nom d'utilisateur. Vous pouvez enregistrer cette partie du chemin, dans un champ séparé. Mais même dans ce cas, vous pouvez le créer dynamiquement en fonction des données enregistrées dans l'enregistrement. J'ai trouvé qu'il était préférable d'enregistrer le moins d'informations de chemin possible. Et ils créent une configuration ou une constante que vous pouvez utiliser à tous les endroits dont vous avez besoin pour mettre le chemin d'accès au fichier.
Aussi le path
et le link
sont très différents, donc en enregistrant uniquement le nom, vous pouvez le lier à n'importe quelle page PHP de votre choix sans avoir à soustraire des données du chemin. J'ai toujours trouvé plus facile d'ajouter au nom de fichier que de soustraire d'un chemin.
Base de données (juste quelques suggestions, l'utilisation peut varier) Comme toujours avec les données, demandez-vous qui, quoi, où, quand
- identifiant -
int
incrémentation automatique de la clé primaire - identifiant_utilisateur -
int
clé étrangère, qui l'a téléchargé - hachage -
char[40] *sha1*, unique
quoi le hachage - nom de hachage -
varchar
{timestampl}-{hash}.{ext} où le nom des fichiers sur le disque dur - nom du fichier -
varchar
le nom d'origine donné par l'utilisateur, de cette façon nous pouvons lui montrer le nom qu'il attend (si c'est important) - statut -
enum[public,private,deleted,pending.. etc]
statut du fichier, selon votre cas d'utilisation, vous devrez peut-être revoir les fichiers, ou peut-être que certains sont privés que seul l'utilisateur peut les voir, peut-être que certains sont publics, etc. - status_date -
timestamp|datetime
moment où le statut a été modifié. - create_date -
timestamp|datetime
quand l'heure à laquelle le fichier a été créé, un horodatage est préférable car il facilite certaines choses, mais il devrait être le même horodatage utilisé dans le nom de hachage, dans ce cas. - type -
varchar
- type mime, peut être utile pour définir le type mime lors du téléchargement, etc.
Si vous vous attendez à ce que différents utilisateurs téléchargent le même fichier et que vous utilisez le file_hash
vous pouvez faire le hash
champ un index unique combiné de user_id
et le hash
de cette façon, cela n'entrerait en conflit que si le même utilisateur téléchargeait le même fichier. Vous pouvez également le faire en fonction de l'horodatage et du hachage, selon vos besoins.
C'est le truc de base auquel je pouvais penser, ce n'est pas un absolu juste quelques champs que je pensais être utiles.
Il est utile d'avoir le hash seul, si vous le stockez seul, vous pouvez le stocker dans un CHAR(40)
pour sha1 (occupe moins d'espace dans la base de données que VARCHAR
) et définissez le classement sur UTF8_bin
qui est binaire. Cela rend les recherches sur celui-ci sensibles à la casse. Bien qu'il y ait peu de possibilité de collision de hachage, cela ajoute juste un peu plus de protection car les hachages sont des lettres majuscules et minuscules.
Vous pouvez toujours construire le hashname
à la volée si vous stockez l'extension et l'horodatage séparément. Si vous vous retrouvez à créer des choses encore et encore, vous voudrez peut-être simplement les stocker dans la base de données pour simplifier le travail en PHP.
J'aime juste mettre le hachage dans le lien, pas d'extension ni rien donc mes liens ressemblent à ça.
http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea
Vraiment simple, vrai générique, sûr dans des urls toujours de la même taille etc..
Le hashname
pour ce "fichier" serait comme ça
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg
Si vous avez des conflits avec le même fichier et un utilisateur différent (ce que j'ai mentionné ci-dessus). Vous pouvez toujours ajouter la partie horodatage dans le lien, le user_id ou les deux. Si vous utilisez le user_id, il peut être utile de le remplir à gauche avec des zéros. Par exemple, certains utilisateurs peuvent avoir ID:1
et certains peuvent être ID:234
pour que vous puissiez le laisser remplir à 4 endroits et les rendre 0001
et 0234
. Ajoutez ensuite cela au hachage, ce qui est presque imperceptible :
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg
La chose importante ici est que parce que sha1
est toujours 40
et l'identifiant est toujours 4
nous pouvons séparer les deux avec précision et facilement. Et de cette façon, vous pouvez toujours le rechercher de manière unique. Il existe de nombreuses options différentes, mais tout dépend de vos besoins.
Accès Comme le téléchargement. Vous devez toujours générer le fichier avec PHP, ne leur donnez pas un accès direct au fichier. Le meilleur moyen est de stocker les fichiers en dehors de la racine Web (au-dessus du public_html
, ou www
dossier ). Ensuite, en PHP, vous pouvez définir les en-têtes sur le type correct et essentiellement lire le fichier. Cela fonctionne pour à peu près tout sauf la vidéo. Je ne gère pas les vidéos donc c'est un sujet en dehors de mon expérience. Mais je trouve qu'il vaut mieux y penser car toutes les données de fichier sont du texte, ce sont les en-têtes qui transforment ce texte en image, en fichier Excel ou en pdf.
Le gros avantage de ne pas leur donner un accès direct au fichier est que si vous avez un site d'adhésion, ou si vous ne voulez pas que votre contenu soit accessible sans connexion, vous pouvez facilement vérifier en PHP s'ils sont connectés avant de leur donner le contenu. Et, comme le fichier se trouve en dehors de la racine Web, ils ne peuvent pas y accéder autrement.
Le plus important est de choisir quelque chose de cohérent, qui reste suffisamment flexible pour répondre à tous vos besoins.
Je suis sûr que je peux en proposer d'autres, mais si vous avez des suggestions, n'hésitez pas à commenter.
FLUX DE PROCESSUS DE BASE
- L'utilisateur soumet le formulaire (
enctype="multipart/form-data"
)
https://www.w3schools.com/tags/att_form_enctype.asp
- Le serveur reçoit le message du formulaire, Super Globals
$_POST
et le$_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)
]
];
-
Vérifiez les erreurs
if(!$_FILES['fielname']['error'])
-
Nettoyer le nom d'affichage
$filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");
-
Enregistrer le fichier, créer un enregistrement de base de données ( PSUDO-CODE )
Comme ceci :
$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 -fichier-uploadé.php
-
Créez un lien (varie en fonction du routage), le moyen le plus simple consiste à créer votre lien comme ceci
http://www.example.com/download?file={$hash}
mais c'est plus moche quehttp://www.example.com/download/{$hash}
-
l'utilisateur clique sur le lien pour accéder à la page de téléchargement.
obtenir INPUT et rechercher l'enregistrement
$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
Etc....
Santé !