I'm trying to unzip a zip file directly from the php://input
stream. I'm running Laravel Homestead, PHP 7.1.3-3+deb.sury.org~xenial+1
, with an endpoint at myproject.app/upload
, here is the curl
command:
curl --request POST \
--url 'http://myproject.app/upload' \
--data-binary "@myfile.zip" \
Here is a list of all the methods I've tried, which all fail:
dd(file_get_contents('compress.zlib://php://input'));
file_get_contents(): cannot represent a stream of type Input as a File Descriptor
$fh = fopen('php://input', 'rb');
stream_filter_append($fh, 'zlib.inflate', STREAM_FILTER_READ, array('window'=>15));
$data = '';
while (!feof($fh)) {
$data .= fread($fh, 8192);
}
dd($data);
""
$zip = new ZipArchive;
$zip->open('php://input');
$zip->extractTo(storage_path() . '/' . 'myfile');
$zip->close();
ZipArchive::extractTo(): Invalid or uninitialized Zip object
Here are all the links I've found on the subject:
http://php.net/manual/en/wrappers.php#83220
http://php.net/manual/en/wrappers.php#109657
http://php.net/manual/en/wrappers.compression.php#118461
https://secure.phabricator.com/rP42566379dc3c4fd01a73067215da4a7ca18f9c17
https://arjunphp.com/how-to-unpack-a-zip-file-using-php/
I'm beginning to think that it's not possible to operate on streams with PHP's built-in zip functionality. The overhead and complexity of writing temporary files would be pretty disappointing. Does anyone know how to do this, or is it a bug?
After more research, I discovered the answer, but it's not satisfactory. Due to one of the great blunders of the modern world, gzip and zip are not the same format. gzip encodes a single file (that's why we often see tar.gz), while zip encodes files and folders. I was trying to upload a zip file and decode it with gzip, which doesn't work. More info:
https://stackoverflow.com/a/20765054/539149
https://stackoverflow.com/a/1579506/539149
The other part of this issue is that PHP neglected to provide a stream filter for gzip:
https://stackoverflow.com/a/11926679/539149
So even though gzopen('php://temp', 'rb')
works, gzopen('php://input', 'rb')
does not because the input stream is not rewindable. This makes it impossible to operate on an in-memory stream, because there is no way to write data to a stream and then read unzipped data on a separate gzip connection to that stream. Which means the following code does not work:
$input = fopen("php://input", "rb");
$temp = fopen("php://temp", "rb+");
stream_copy_to_stream($input, $temp);
rewind($temp);
dd(stream_get_contents(gzopen('php://temp', 'rb')));
People have attempted various workarounds, but they all do bit fiddling:
http://php.net/manual/en/function.gzopen.php#105676
http://php.net/manual/en/function.gzdecode.php#112200
I did manage to get a pure in-memory solution to work, but since it's not possible to use streams, a needless copy occurs:
// works (stream + string)
dd(gzdecode(file_get_contents('php://input')));
// works (stream + file)
dd(stream_get_contents(gzopen(storage_path() . '/' . 'myfile.gz', 'rb')));
// works (stream + file)
dd(file_get_contents('compress.zlib://' . storage_path() . '/' . 'myfile.gz'));
// doesn't work (stream)
dd(stream_get_contents(gzopen('php://input', 'rb')));
// doesn't work (stream + filter)
dd(file_get_contents('compress.zlib://php://input'));
Without a working example, I have to assume that PHP's zip implementation is incomplete because it can't operate on streams. If anyone has more information, I'm happy to revisit this. Please post any examples or repositories that implement zipped uploads/downloads via streams, thanks!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With