Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unzip or inflate php://input stream?

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?

like image 699
Zack Morris Avatar asked Jul 08 '17 02:07

Zack Morris


1 Answers

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!

like image 135
Zack Morris Avatar answered Nov 10 '22 12:11

Zack Morris