Avoiding permissions problems when creating Zip files in PHP

A typical PHP snippet to create a Zip file looks something like this:

$zip = new ZipArchive();
$zipname = 'package_name.zip';
if ( true === $zip->open( $zipname, ZipArchive::CREATE ) ) {
    $zip->addFromString( 'file_name.txt', $file_contents );
    $zip->close();
    header( 'Content-Type: application/zip' );
    header( 'Content-disposition: attachment; filename=' . $zipname );
    header( 'Content-Length: ' . filesize( $zipname ) );
    readfile( $zipname );
    unlink( $zipname );
    exit;
}

But did you ever stop to think about where the temporary ‘package_name.zip’ file is created? Neither have I. What I have come across, are permissions errors when creating zip files as well as the slightly more mysterious 0Kb Zip files.

Watching the filesystem and removing the unlink() reveals that the Zip file is created in the current working directory, i.e. “the path of the ‘main’ script referenced in the URL”. For most web applications or CMS this will mean the root of the application/CMS.

This is fine when you have fairly relaxed permissions, but if things are more locked down you end up with the errors described above as the temporary file can’t be created.

A simple solution

As is so often the case the solution is actually very simple – tell PHP to move it’s working directory using the chdir() function. To keep things re-usable, we can combine chdir() with another function that returns the temporary directory specified in php.ini – which should always be writable by PHP – sys_get_temp_dir().

Below is the updated snippet, with one extra feature – it stores the temp file with a random name to reduce the liklihood of collisions if multiple people access your script at once.

chdir( sys_get_temp_dir() ); // Zip always get's created in current working dir so move to tmp.
$zip = new ZipArchive;
$tmp_zipname = uniqid(); // Generate a temp UID for the file on disk.
$zipname = 'package_name.zip'; // True filename used when serving to user.
if ( true === $zip->open( $tmp_zipname, ZipArchive::CREATE ) ) {
    $zip->addFromString( 'file_name.txt', $file_contents );
    $zip->close();
    header( 'Content-Type: application/zip' );
    header( 'Content-disposition: attachment; filename=' . $zipname );
    header( 'Content-Length: ' . filesize( $tmp_zipname ) );
    readfile( $tmp_zipname );
    unlink( $tmp_zipname );
    exit;
}

Leave a Reply

Your email address will not be published.