Par

PHP : download sécurisé de fichiers

Cet article est une réponse « Créer un accès sécurisé en PHP pour un contenu volumineux » de Johan. Je propose ici une autre méthode pour sécuriser le téléchargement de tout type de fichier. Cet méthode à l’avantage par rapport à la sienne de ne pas nécessiter de copie de fichier. Il n’y a donc pas d’espace disque supplémentaire utilisé.

Le principe :

  • Un dossier contenant les médias, qui n’est pas accessibles depuis le Web.
  • Un fichier PHP qui transmet le contenu du média demandé.

Sécurité

Attention, il ne faut pas passer en paramètre le chemin direct vers votre fichier, un utilisateur
mal intentionné pourrait exploiter cela pour récupérer n’importe quel fichier sur votre serveur
Web !
Il faut envoyer un identifiant de votre choix que vous faites correspondre à un chemin vers
le fichier demandé par l’intermédiaire de votre choix (simple switch, base de données, etc.)

Transfert

Le transfert du fichier ne doit pas se faire avec n’importe quelle fonction. La fonction file_get_contents par exemple n’est pas appropriée car elle place le fichier dans une chaîne de caractères et cela peut altérer les données ! Le plus simple est d’utiliser la fonction Il faut utiliser la fonction readfile qui fait tout à votre place ! Il est important d’ajouter les bonnes en-têtes au document pour que le navigateur interprète correctement les données reçues.

<?php // Attention ! ceci doit être la toute première ligne de votre fichier PHP.
// Si votre fichier est encodé en UTF-8 pensez à l'enregistrer sans BOM !
 
// Vérification des droits d'accès, à adapter à votre code !
session_start();
if (!isset($_SESSION['un_utilisateur_est_connecte']))
  die('Erreur - Il faut être connecté.');
 
/*
* Appel d'une fonction qui récupère le chemin du fichier et son type.
* Vous pouvez aussi vérifier dans cette fonction les droits d'accès pour l'utilisateur connecté.
*/
$data = recuperer_les_infos_de_mon_fichier($_GET['id']);
if (!$data)
  die('Erreur');
 
 
// Fix pour IE et ses problèmes de cache !
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
switch ($data['type']) {
  case "pdf": $content_type="application/pdf"; break;
  case "zip": $content_type="application/zip"; break;
  // Pour une liste plus complète des types de fichier, voir le lien ci-dessous.
  default: die("Erreur - Type de fichier non reconnu.");
}
header("Content-Type: $content_type");
header("Content-Disposition: attachment; filename="".basename($data['fichier'])."";");
// Mode binaire : pour éviter toute altération des données
header("Content-Transfer-Encoding: binary");
// Content-Length : indiquer la taille du fichier, ce qui permet au navigateur de l'utilisateur d'estimer le temps restant.
header("Content-Length: "[email protected]filesize($data['fichier']));
set_time_limit(0);
@readfile("$data['fichier']") or die("Fichier introuvable.");

Content-Type

Pour que le navigateur de l’utilisateur reconnaisse le fichier que vous envoyez et l’ouvre avec la bonne application, il faut spécifier le type MIME dans la directive Content-Type : lien vers une liste de types.

Problèmes éventuels

  • Sur certains serveurs ayant des restrictions de sécurités, la fonction set_time_limit peut être désactivée (cf. safe_mode de PHP). Dans ce cas, le temps d’exécution du script sera limité et pour un fichier volumineux, vous ne pourrez utiliser cette méthode.
  • Dans certains cas, Internet Explorer peut poser des problèmes en mettant tout de même le fichier dans le cache, notamment si vous utilisez le protocole HTTPS.