Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use flysystem stream in file request #180

Merged
merged 4 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### 4.1.0
- **[FEATURE]**: PIMCORE 10.5 support only
- **[ENHANCEMENT]**: Add public asset path protection, read more about it [here](./docs/200_Restrictions.md#public-assets-path-protection)
- **[ENHANCEMENT]**: Respect flysystem stream in asset/zip download, read more about it [here](./docs/240_AssetProtection.md#package) | [#174|@aarongerig](https://github.com/dachcom-digital/pimcore-members/pull/174)

### 4.0.4
- **[BUGFIX]**: assert non list array after filtering user roles [@dasraab](https://github.com/dachcom-digital/pimcore-members/pull/169)
Expand Down
7 changes: 5 additions & 2 deletions docs/240_AssetProtection.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ $downloadLink = $this->restrictionUri->generateAssetUrl($download);

### Package
If you want to get multiple downloads at once, you can use the generateAssetPackageUrl() method.
This will create a zip file on the fly, so no temp files on your server!
This will create a temporary zip file containing all requested files!

>**Important:** Your server need the zip module to create streamed zip files!
>**Important:** Since we need to fetch all assets via stream by flysystem, it is no longer possible to push the zip file to the client on the fly.
> Therefor we need to store the archive in `PIMCORE_SYSTEM_TEMP_DIRECTORY` until the file has been downloaded.
>
> Depending on your system and archive size, this can temporarily consume a lot of space and memory!

```php
<?php
Expand Down
71 changes: 20 additions & 51 deletions src/MembersBundle/Controller/RequestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@
namespace MembersBundle\Controller;

use Pimcore\Model;
use Pimcore\Tool\Console;
use MembersBundle\Configuration\Configuration;
use MembersBundle\Security\RestrictionUri;
use Pimcore\Tool\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\Stream;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;

class RequestController extends AbstractController
{
public const BUFFER_SIZE = 8192;

public function __construct(
protected RouterInterface $router,
protected Storage $storage,
Expand All @@ -27,7 +24,7 @@ public function __construct(
) {
}

public function serveAction(Request $request, ?string $hash = null): StreamedResponse
public function serveAction(Request $request, ?string $hash = null): Response
{
if ($this->configuration->getConfig('restriction')['enabled'] === false) {
throw $this->createNotFoundException('members restriction has been disabled.');
Expand Down Expand Up @@ -112,78 +109,50 @@ private function servePath(string $path): Response

private function serveFile(Model\Asset $asset): StreamedResponse
{
$contentType = $asset->getMimetype();
$fileSize = $asset->getFileSize();
$response = new StreamedResponse(static function () use ($asset) {
fpassthru($asset->getStream());
});

$response = new StreamedResponse();
$response->setStatusCode(200);
$response->headers->set('Content-Type', $contentType);
$response->headers->set('Content-Type', $asset->getMimetype());
$response->headers->set('Connection', 'Keep-Alive');
$response->headers->set('Expires', 0);
$response->headers->set('Provider', 'Pimcore-Members');
$response->headers->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
$response->headers->set('Pragma', 'public');
$response->headers->set('Content-Length', $fileSize);
$response->headers->set('Content-Length', $asset->getFileSize());
$response->headers->set('Content-Disposition', $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
\Pimcore\File::getValidFilename(basename($asset->getFileName()))
));

$response->setCallback(function () use ($asset) {
flush();
ob_flush();
$handle = fopen(rawurldecode($asset->getLocalFile()), 'rb');
while (!feof($handle)) {
echo fread($handle, self::BUFFER_SIZE);
flush();
ob_flush();
}
});

return $response;
}

/**
* @throws \Exception
*/
private function serveZip(array $assets): StreamedResponse
private function serveZip(array $assets): BinaryFileResponse
{
$fileName = 'package.zip';
$files = '';
$fileName = 'package';
$tempZipPath = sprintf('%s/%s-%s.zip', PIMCORE_SYSTEM_TEMP_DIRECTORY, uniqid('', false), $fileName);

$archive = new \ZipArchive();
$archive->open($tempZipPath, \ZipArchive::CREATE);

/** @var Model\Asset $asset */
foreach ($assets as $asset) {
$filePath = rawurldecode($asset->getLocalFile());
$files .= '"' . $filePath . '" ';
$archive->addFromString($asset->getFilename(), stream_get_contents($asset->getStream()));
}

$response = new StreamedResponse();
$response->setStatusCode(200);
$archive->close();

$response = new BinaryFileResponse(new Stream($tempZipPath));
$response->deleteFileAfterSend(true);

$response->headers->set('Content-Type', 'application/zip');
$response->headers->set('Content-Transfer-Encoding', 'binary');
$response->headers->set('Content-Disposition', $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$fileName
sprintf('%s.zip', $fileName)
));

$zibLib = Console::getExecutable('zip');
if (empty($zibLib)) {
throw new NotFoundHttpException('zip extension not found on this server.');
}

$response->setCallback(function () use ($files) {
mb_http_output('pass');
flush();
ob_flush();
$handle = popen('zip -r -j - ' . $files, 'r');
while (!feof($handle)) {
echo fread($handle, self::BUFFER_SIZE);
flush();
ob_flush();
}
pclose($handle);
});

return $response;
}
}