26 votes

PHP ZipArchive corrompu sous Windows

J'utilise la classe ZipArchive de PHP pour créer un fichier zip contenant des photos et l'envoyer au navigateur pour téléchargement. Voici mon code :

/**
 * Grabs the order, packages the files, and serves them up for download.
 *
 * @param string $intEntryID 
 * @return void
 * @author Jesse Bunch
 */
public static function download_order_by_entry_id($intUniqueID) {

    $objCustomer = PhotoCustomer::get_customer_by_unique_id($intUniqueID);

    if ($objCustomer):

        if (!class_exists('ZipArchive')):
            trigger_error('ZipArchive Class does not exist', E_USER_ERROR);
        endif;

        $objZip = new ZipArchive();
        $strZipFilename = sprintf('%s/application/tmp/%s-%s.zip', $_SERVER['DOCUMENT_ROOT'], $objCustomer->getEntryID(), time());

        if ($objZip->open($strZipFilename, ZIPARCHIVE::CREATE) !== TRUE):

            trigger_error('Unable to create zip archive', E_USER_ERROR);

        endif;          

        foreach($objCustomer->arrPhotosRequested as $objPhoto):

            $filename = PhotoCart::replace_ee_file_dir_in_string($objPhoto->strHighRes);
            $objZip->addFile($filename,sprintf('/press_photos/%s-%s', $objPhoto->getEntryID(), basename($filename)));

        endforeach;

        $objZip->close();

        header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($strZipFilename)).' GMT',  TRUE, 200);
        header('Cache-Control: no-cache', TRUE);
        header('Pragma: Public', TRUE);
        header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT', TRUE);
        header('Content-Length: '.filesize($strZipFilename), TRUE);
        header('Content-disposition: attachment; filename=press_photos.zip', TRUE);

        header('Content-Type: application/octet-stream', TRUE);

        ob_start();
        readfile($strZipFilename);
        ob_end_flush();
        exit;

    else:

        trigger_error('Invalid Customer', E_USER_ERROR);

    endif;

}

Ce code fonctionne très bien avec tous les navigateurs sauf IE. Dans IE, le fichier se télécharge correctement, mais l'archive zip est vide. Lorsque j'essaie d'extraire les fichiers, Windows me dit que l'archive zip est corrompue. Quelqu'un a-t-il déjà rencontré ce problème ?

Modifier Mise à jour : Après une suggestion de @profitphp, j'ai changé mes en-têtes en ceci :

header("Cache-Control: public");
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
//header("Content-Description: File Transfer");
//header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=\"pressphotos.zip\"");
//header("Content-Transfer-Encoding: binary");
header("Content-length: " . filesize($strZipFilename));

Voici également une capture d'écran de l'erreur dans Windows après ouverture avec Firefox :

alt text

Cette erreur se produit à la fois dans IE et Firefox sous Windows. Elle fonctionne bien sur Mac. De plus, sous Windows, la taille des fichiers semble correcte :

alt text

Edit #2 Ce problème est résolu. Voir ma réponse ci-dessous.

3voto

profitphp Points 4883

J'ai eu des problèmes avec ça avant. Essayez d'enlever l'en-tête content type. Voici le code que j'ai trouvé et qui fonctionne dans IE et FF. Remarquez les lignes commentées, j'avais les mêmes problèmes avec différentes combinaisons de ces lignes.

header("Cache-Control: public");
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
//header("Content-Description: File Transfer");
//header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=\"adwords-csv.zip\"");
//header("Content-Transfer-Encoding: binary");
header("Content-length: " . filesize($filename));

3voto

Mark Points 1

J'ai ce problème depuis une heure. Après avoir essayé 10 solutions différentes, je l'ai résolu en m'assurant que le script existe après la sortie du fichier ZIP :

            readfile($zip_name);
            unlink($zip_name);
            **exit();**

2voto

Shakus Points 381

En plus de ce que les autres ont suggéré, il est important de faire attention à vos noms de fichiers et de répertoires car Windows n'aime pas nécessairement les chemins et les noms de fichiers de Linux. Il arrive aussi qu'il les échappe différemment lors de la compression. Les exemples sont nombreux, mais le plus important

  • Fichiers *points (. et ..), fichiers avec seulement des différences de casse (nom.txt et NOM.txt),
  • chemins de fichiers absolus (/tmp/file.txt)*.
  • Certains autres caractères qui sont autorisés dans les noms de fichiers sous Windows peuvent causer des problèmes lorsque l'Explorateur Windows est utilisé pour ouvrir des fichiers. Dans mon cas, le caractère ':' a été la cause du problème, mais il a fallu beaucoup de travail pour le découvrir.

Donc avant de vous lancer dans l'utilisation d'un grand nombre de paramètres via exec('zip ...'), je vous suggère de suivre une procédure simple :

  1. Localisez le dossier ou le fichier que votre site web zippe.
  2. courir : zip -9 -r -k zip-noms-modifiés.zip /path/to/votre/dossier
  3. faites attention à ce que la console crache. Dans mon cas, les " :" dans les noms de fichiers ont été supprimés.
  4. Déplacez le fichier zip sur une machine Windows et essayez de l'ouvrir.

Si cela fonctionne, il est préférable de supprimer les caractères qui ont été supprimés par l'option -k de vos noms de fichiers/répertoires et d'essayer de les compresser normalement. Notez que certains paramètres, comme -k, ont des effets secondaires. Dans ce cas, -k est en contradiction avec l'option -q (pour les liens sym).

L'option -k peut également rendre les noms de vos fichiers illisibles. Dans mon cas, mes fichiers étaient nommés en fonction de l'heure de création (par exemple 10:55:39.pdf) pour faciliter la localisation de l'enregistrement requis dans les archives, mais l'option -k l'a transformé en 105539.pdf, ce qui n'est pas facilement lisible par les utilisateurs. J'ai donc changé les noms en 10_55_39.pdf qui s'ouvre sous Windows sans utiliser l'option -k mais qui est toujours lisible.

En outre, l'utilisation de PCLZip vous faciliterait grandement la vie, car vous pouvez ajouter un dossier entier en une seule fois et modifier le chemin des fichiers en une seule ligne. Dans mon cas, je supprime /tmp/directory/ de mes fichiers zip avec les deuxième et troisième paramètres, ce qui évite un autre problème de compatibilité avec Windows (avoir un chemin absolu dans les fichiers zip) :

$v_list = $zip->create( $sourceFolder, PCLZIP_OPT_REMOVE_PATH, $sourceFolder . DIRECTORY_SEPARATOR);
if ($v_list == 0) {
    throw new \Exception($zip->errorInfo(true));
}

2voto

Joshy Francis Points 92
            ob_clean(); //very important
            // http headers for zip downloads
            header("Pragma: public");
            header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
            header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
            header("Cache-Control: public");
            header('Content-Type: application/x-download');
            header("Content-Disposition: attachment; filename=\"filename.zip\"");
            header("Content-Length: ".filesize($filepath ));

            @readfile($filepath );
            unlink($filepath);  //very important
            exit;//very important

Cela a fonctionné pour moi pendant que j'essayais les solutions ci-dessus.

1voto

pu-c Points 11

J'ai eu le même problème. Ce code a fonctionné pour moi, mais j'ai dû insérer la première ligne dans mon fichier php ! Si je mettais le code au milieu du fichier, ça ne marchait pas. Peut-être des problèmes d'encodage ? !

// Prepare File
$file = tempnam("tmp", "zip");
$zip = new ZipArchive();
$zip->open($file, ZipArchive::OVERWRITE);

// Staff with content
$zip->addFile($filepathOnServer, 'mypic.jpg');

// Close and send to users
$zip->close();
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="filename.zip"');
readfile($file);
unlink($file);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X