Zack Morris
Zack Morris

Reputation: 4823

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?

Upvotes: 6

Views: 4397

Answers (1)

Zack Morris
Zack Morris

Reputation: 4823

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.

Upvotes: 8

Related Questions