web-dev-qa-db-fra.com

Téléchargement de fichier depuis une racine Web externe

J'ai configuré du code pour télécharger les fichiers Zip existant dans un dossier situé au-dessus de la racine Web. Le téléchargement sera déclenché à partir d'une page de compte d'utilisateur dans WordPress. Ils n'ont pas besoin d'être sécurisés au niveau de la banque, mais j'aimerais empêcher l'accès direct aux fichiers depuis l'extérieur du site et les rendre accessibles uniquement aux utilisateurs disposant des niveaux d'autorisation appropriés et uniquement à partir de la page de compte de l'utilisateur approprié. Le site est entièrement https.

Le dossier où résident les fichiers Zip est protégé via htaccess.

Chaque utilisateur affecté à un rôle d'utilisateur spécifique verra un lien de téléchargement sur sa page "Compte":

if(current_user_can('download_these_files')){
    $SESSION['check'] = 'HVUKfb0IG1HIzHxJj5fZ';
    ?>
        <form class="user-file-download-form" action="/download.php" method="POST">
            <input type="submit" name="submit" value="Download File">
        </form>
    <?php
}

Ce formulaire est envoyé à download.php, qui se trouve à la racine du site Web et contient du code que j'ai reconstitué avec l'aide de Google.

session_start();
if( isset( $_POST['submit'] ) ){
    $check = $_SESSION['check'];
    if( $check === 'HVUKfb0IG1HIzHxJj5fZ' ){
        $file = /path/to/file/above/root.Zip;
        header('Content-Description: File Transfer');
        header('Content-Type: application/Zip');
        header('Content-Disposition: attachment; filename=' . basename($file));
        header('Content-Transfer-Encoding: binary');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: ' . filesize($file));
        ob_clean();
        flush();
        readfile( $file );
        exit;
    }else{
        header( 'Location: https://example.com/404page/' );
}else{
    header( 'Location: https://example.com/404page/' );
}

Cela fonctionne parfaitement. Mais je ne peux pas m'empêcher de me demander si je devrais faire quelque chose différemment. J'espérais savoir si cette mise en œuvre était prête pour la production ou s'il me manquait quelque chose d'important, car c'était la première fois que j'essayais de procéder de la sorte.

Je vous remercie.

1
Rob

Ce que vous avez est prêt pour la production. Cependant, il y a place pour quelques améliorations mineures, je vais donc les signaler pour vous. Voir aussi mes notes ci-dessous concernant X-Sendfile et X-Accel-Redirect.


Remplacez ces lignes:

ob_clean();
flush();

avec ce qui suit:

while (@ob_end_clean());

Le fait est que s'il y a déjà quelque chose dans la mémoire tampon de sortie, vous ne voulez pas le vider, vous voulez seulement le nettoyer. Si vous le videz, vous allez préfixer le contenu de votre fichier téléchargeable avec le contenu du tampon de sortie, ce qui ne servirait qu'à corrompre le fichier téléchargeable. Voir: http://php.net/manual/en/function.ob-end-clean.php


Après cette ligne:

$file = /path/to/file/above/root.Zip;

Ajoutez ce qui suit pour vous assurer que la compression GZIP au niveau du serveur est désactivée. Cela pourrait ne pas faire de différence chez votre hébergeur actuel, mais déplacez le site ailleurs et sans ces lignes, vous pourriez voir le script se rompre.

@ini_set('zlib.output_compression', 0);
if(function_exists('Apache_setenv')) @Apache_setenv('no-gzip', '1');
header('Content-Encoding: none');

Attention:Faites attention de ne pas utiliser cette technique de téléchargement de fichier pilotée par PHP sur des fichiers plus volumineux (plus de 20 Mo, par exemple). Pourquoi? Deux raisons:

  • PHP a une limite de mémoire interne. Si readfile() dépasse cette limite lors de la lecture du fichier en mémoire et de sa transmission à un visiteur, votre script échouera.

  • De plus, les scripts PHP ont également une limite de temps. Si un visiteur sur une connexion très lente met longtemps à télécharger un fichier plus volumineux, le script expire et l'utilisateur rencontre un échec de téléchargement ou reçoit un fichier partiel/corrompu.


Attention:Notez également que les téléchargements de fichiers basés sur PHP et utilisant la technique readfile() ne prennent pas en charge les plages d'octets pouvant être repris. Donc, suspendre le téléchargement, ou le téléchargement étant interrompu d'une manière ou d'une autre, ne laisse pas à l'utilisateur une option pour le reprendre. Ils devront recommencer le téléchargement. Il est possible de supporter les requêtes Range (resume) en PHP, mais c'est fastidieux .


À long terme, je suggère que vous commenciez à rechercher un moyen beaucoup plus efficace de servir les fichiers protégés, appelé X-Sendfile dans Apache, et X-Accel-Redirect dans Nginx.

X-Sendfile et X-Accel-Redirect fonctionnent tous deux sur le même concept sous-jacent. Au lieu de demander à un langage de script tel que PHP de récupérer un fichier en mémoire, il vous suffit d'indiquer au serveur Web d'effectuer une redirection interne et de servir le contenu d'un fichier autrement protégé. En bref, vous pouvez supprimer une grande partie de ce qui précède et réduire la solution à header('X-Accel-Redirect: ...').

2
jaswrks