In memory download and extract zip archive - php

I would like to download a zip archive and unzip it in memory using PHP.
This is what I have today (and it's just too much file-handling for me :) ):
// download the data file from the real page
copy("http://www.curriculummagic.com/AdvancedBalloons.kmz", "./data/zip.kmz");
// unzip it
$zip = new ZipArchive;
$res = $zip->open('./data/zip.kmz');
if ($res === TRUE) {
$zip->extractTo('./data');
$zip->close();
}
// use the unzipped files...

Warning: This cannot be done in memory — ZipArchive cannot work with "memory mapped files".
You can obtain the data of a file inside a zip-file into a variable (memory) with file_get_contentsDocs as it supports the zip:// Stream wrapper Docs:
$zipFile = './data/zip.kmz'; # path of zip-file
$fileInZip = 'test.txt'; # name the file to obtain
# read the file's data:
$path = sprintf('zip://%s#%s', $zipFile, $fileInZip);
$fileData = file_get_contents($path);
You can only access local files with zip:// or via ZipArchive. For that you can first copy the contents to a temporary file and work with it:
$zip = 'http://www.curriculummagic.com/AdvancedBalloons.kmz';
$file = 'doc.kml';
$ext = pathinfo($zip, PATHINFO_EXTENSION);
$temp = tempnam(sys_get_temp_dir(), $ext);
copy($zip, $temp);
$data = file_get_contents("zip://$temp#$file");
unlink($temp);

As easy as:
$zipFile = "test.zip";
$fileInsideZip = "somefile.txt";
$content = file_get_contents("zip://$zipFile#$fileInsideZip");

Old subject but still relevant since I asked myself the same question, without finding an answer.
I ended up writing this function which returns an array containing the name of each file contained in the archive, as well as the decompressed contents of that file:
function GetZipContent(String $body_containing_zip_file) {
$sectors = explode("\x50\x4b\x01\x02", $data);
array_pop($sectors);
$files = explode("\x50\x4b\x03\x04", implode("\x50\x4b\x01\x02", $sectors));
array_shift($files);
$result = array();
foreach($files as $file) {
$header = unpack("vversion/vflag/vmethod/vmodification_time/vmodification_date/Vcrc/Vcompressed_size/Vuncompressed_size/vfilename_length/vextrafield_length", $file);
array_push($result, [
'filename' => substr($file, 26, $header['filename_length']),
'content' => gzinflate(substr($file, 26 + $header['filename_length'], -12))
]);
}
return $result;
}
Hope this is useful ...

You can get a stream to a file inside the zip and extract it into a variable:
$fp = $zip->getStream('test.txt');
if(!$fp) exit("failed\n");
while (!feof($fp)) {
$contents .= fread($fp, 1024);
}
fclose($fp);

If you can use system calls, the simplest way should look like this (bzip2 case). You just use stdout.
$out=shell_exec('bzip2 -dkc '.$zip);

Related

PHP: how to retrieve the filename of the file contained in .gz?

My test.gz contains for instance : image1.jpg.
How can I decompress the test.gz so it gives me image1.jpg ?
I tried that method:
$file = gzopen($file_name, 'rb');
$out_file = fopen($out_file_name, 'wb');
but it supposes you need to know the filename contained in the .gz file
regards
You need to use PHP PharData to extract and read its content.
To extract tar or tar.gz :
$phar = new PharData('myphar.tar');
$phar->extractTo('/full/path');
And to list all files, you can use :
archive = new PharData('/upload/directory/myphar.tar.gz');
foreach (new RecursiveIteratorIterator($archive) as $file) {
echo $file . "<br />";
}

PHP ZipArchive isn't creating a gs:// zip file in Google AppEngine, Warning: filesize(): stat failed for gs://

I'm able to directly write and read files in Google Storage, but when I try to use ZipArchive to create a file, it fails. Google says that the zip extension is enabled in the GAE.
$tmpdirectory .= 'gs://#default#/tmp/user-'.$uid;
$uniqueid = uniqid() . time();
$user_visible_filename = 'export.zip';
$output_path = sprintf("%s/export.%s.zip", $tmpdirectory, $uniqueid);
$zip = new ZipArchive;
$res = $zip->open($output_path, ZipArchive::CREATE);
if ($res === true) {
foreach ($data as $datatype => $records) {
$filename = sprintf("%s/%s.csv", $tmpdirectory, $datatype);
write_csv_to_filename($records, $filename);
$localname = basename($filename);
$fileresult = $zip->addFromString($localname, file_get_contents($filename));
print "adding $localname... num files in zip: ".($fileresult ? "true" : "false")." -> ".$zip->numFiles."<br/>\n";
}
}
$closeresult = $zip->close();
print "user_visible_filename: $user_visible_filename<br/>\n";
print "zip filename: $output_path<br/>\n";
print "file size: ".filesize($output_path)."<br/>\n";
header('Content-Type: application/zip');
header('Content-Length: '.filesize($output_path));
header('Content-Disposition: attachment; filename=' . $user_visible_filename);
The above code writes some csv files and I want to bundle them together in a zip file and have their browser download it. I know that the above headers() won't work because I'm printing stuff about before them; I'm going printing the stuff out to debug what's going wrong.
I am able to write each of the CSVs to gs:// and I'm able to access their correct filesize and read their contents back after writing them.
However, when I try to read the filesize() of the zip file (gs://#default#/tmp/user-152/export.5b4565bda18481531274685.zip) it craps out with a big warning message (cannot stat file) and stack trace as if the file doesn't exist.
$zip->close(); returns false which means there was a failure, but I have no idea why it failed.
I found an alternative solution that worked: TbsZip
require_once '../inc/tbszip.php';
$tmpfile = tempnam('/tmp', 'export');
$zip = new clsTbsZip();
$zip->CreateNew($tmpfile);
foreach ($data as $datatype => $records) {
$filename = sprintf("%s/%s.csv", $tmpdirectory, $datatype);
write_csv_to_filename($records, $filename);
$localname = basename($filename);
//$fileresult = $zip->addFile($filename, $localname);
$zip->FileAdd($localname, file_get_contents($filename), TBSZIP_STRING);
}
// Flush() will send all the required headers for downloading the file.
$zip->Flush(TBSZIP_DOWNLOAD, $user_visible_filename, 'application/zip');
$zip->Close();

use PHP ZipArchive with file as variable

In a symfony Controller I have a file (UploadedFile object) as a variable.
I want to open this "variable" with php ZipArchive, and extract it.
But the open() methods is expecting a string, which is the filename in the filesystem. Is there any way to process the file with the ZipArchive and without writing the file variable to the FS?
You can use tmpfile() to create a temporary file, write to it and then use it in the zip. Example:
<?php
$zip = new ZipArchive();
$zip->open(__DIR__ . '/zipfile.zip', ZipArchive::CREATE);
$fp = tmpfile();
fwrite($fp, 'Test');
$filename = stream_get_meta_data($fp)['uri'];
$zip->addFile($filename, 'filename.txt');
$zip->close();
fclose($fp);
Little improve:
$zipContent; // in this variable could be ZIP, DOCX, XLSX etc.
$fp = tmpfile();
fwrite($fp, $zipContent);
$stream = stream_get_meta_data($fp);
$filename = $stream['uri'];
$zip = new ZipArchive();
$zip->open($filename);
// profit!
$zip->close();
fclose($fp);
Just make no sense to create "zipfile.zip" and add file inside, because we already have a variable.
Check out this answer at https://stackoverflow.com/a/53902626/859837
There is a way to create a file which resides in memory only.

get_file_contents of zipped file

I've got a ZIP file sitting on my server. I want to unzip it and then save the completely file contents into just one variable.
I do NOT want to save the unzipped file on my server or on a visitor's computer. I just need all of the contents of that zipped file stored in a variable that I can play around with and eventually show on the screen. Every other solution I've found for this problem includes resaving the file in unzipped form.
How can I do this with get_file_contents, or any other function?
You can simply find out the file names within the ZIP Archive with PHPs ZipArchive for example. try this (have not tested it but you should be able to get it to work) :
$za = new ZipArchive();
$za->open('archive.zip');
$fileContents = array();
for( $i = 0; $i < $za->numFiles; $i++ ) {
$stat = $za->statIndex( $i );
$fp = $z->getStream($stat['name']);
if(!$fp) exit("failed\n");
$contents = '';
while (!feof($fp)) {
$contents .= fread($fp, 1000);
}
fclose($fp);
$fileContents[$stat['name']] = $contents;
}
Once you know the names of the files within the Zip you can also use something like this:
$path = sprintf('zip://%s#%s', $zipArchive, $fileNameInZipArchive);
$fileData = file_get_contents($path);

getimagesize() on stream instead of string

I'm using Valum's file uploader to upload images with AJAX. This script submits the file to my server in a way that I don't fully understand, so it's probably best to explain by showing my server-side code:
$pathToFile = $path . $filename;
//Here I get a file not found error, because the file is not yet at this address
getimagesize($pathToFile);
$input = fopen('php://input', 'r');
$temp = tmpfile();
$realSize = stream_copy_to_stream($input, $temp);
//Here I get a string expected, resource given error
getimagesize($input);
fclose($input);
$target = fopen($pathToFile, 'w');
fseek($temp, 0, SEEK_SET);
//Here I get a file not found error, because the image is not at the $target yet
getimagesize($pathToFile);
stream_copy_to_stream($temp, $target);
fclose($target);
//Here it works, because the image is at the desired location so I'm able to access it with $pathToFile. However, the (potentially) malicious file is already in my server.
getimagesize($pathToFile);
The problem is that I want to perform some file validation here, using getimagesize(). getimagesize only supports a string, and I only have resources available, which result in the error: getimagesize expects a string, resource given.
It does work when I perform getimagesize($pathTofile) at the end of the script, but then the image is already uploaded and the damage could already have been done. Doing this and performing the check afterwards and then maybe deleting te file seems like bad practice to me.
The only thing thats in $_REQUEST is the filename, which i use for the var $pathToFile. $_FILES is empty.
How can I perform file validation on streams?
EDIT:
the solution is to first place the file in a temporary directory, and perform the validation on the temporary file before copying it to the destination directory.
// Store the file in tmp dir, to validate it before storing it in destination dir
$input = fopen('php://input', 'r');
$tmpPath = tempnam(sys_get_temp_dir(), 'upl'); // upl is 3-letter prefix for upload
$tmpStream = fopen($tmpPath, 'w'); // For writing it to tmp dir
stream_copy_to_stream($input, $tmpStream);
fclose($input);
fclose($tmpStream);
// Store the file in destination dir, after validation
$pathToFile = $path . $filename;
$destination = fopen($pathToFile, 'w');
$tmpStream = fopen($tmpPath, 'r'); // For reading it from tmp dir
stream_copy_to_stream($tmpStream, $destination);
fclose($destination);
fclose($tmpStream);
PHP 5.4 now supports getimagesizefromstring
See the docs:
http://php.net/manual/pt_BR/function.getimagesizefromstring.php
You could try:
$input = fopen('php://input', 'r');
$string = stream_get_contents($input);
fclose($input);
getimagesizefromstring($string);
Instead of using tmpfile() you could make use of tempnam() and sys_get_temp_dir() to create a temporary path.
Then use fopen() to get a handle to it, copy over the stream.
Then you've got a string and a handle for the operations you need to do.
//Copy PHP's input stream data into a temporary file
$inputStream = fopen('php://input', 'r');
$tempDir = sys_get_temp_dir();
$tempExtension = '.upload';
$tempFile = tempnam($tempDir, $tempExtension);
$tempStream = fopen($tempFile, "w");
$realSize = stream_copy_to_stream($inputStream, $tempStream);
fclose($tempStream);
getimagesize($tempFile);

Categories