mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-16 14:33:14 +00:00
Use power of symfony binary file response
Add commands Fix typo Remove extra line
This commit is contained in:

committed by
Romain Neutron

parent
7fc9eb3010
commit
01a36ee9f7
@@ -99,6 +99,7 @@ use Alchemy\Phrasea\Core\Provider\TaskManagerServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\TemporaryFilesystemServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\TokensServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\UnicodeServiceProvider;
|
||||
use Alchemy\Phrasea\Core\Provider\XSendFileMappingServiceProvider;
|
||||
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||
use Alchemy\Phrasea\Twig\JSUniqueID;
|
||||
use Alchemy\Phrasea\Twig\Camelize;
|
||||
@@ -306,6 +307,12 @@ class Application extends SilexApplication
|
||||
$this->register(new ValidatorServiceProvider());
|
||||
|
||||
$this->register(new XPDFServiceProvider());
|
||||
$this->register(new XSendFileMappingServiceProvider(), array(
|
||||
'xsendfile.mapping' => array(
|
||||
$this['root.path'] . '/tmp/download/' => '/download/',
|
||||
$this['root.path'] . '/tmp/lazaret/' => '/lazaret/'
|
||||
)
|
||||
));
|
||||
$this->register(new FileServeServiceProvider());
|
||||
|
||||
$this['phraseanet.exception_handler'] = $this->share(function ($app) {
|
||||
|
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Command\Setup;
|
||||
|
||||
use Alchemy\Phrasea\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* This command dumps XsendFile Apache condifuration
|
||||
*/
|
||||
class XSendFileMappingApacheDumper extends Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('xsendfile:dump-apache');
|
||||
|
||||
$this->setDescription('Dump XSendFile mapping for Apache web server');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$mapper = $this->container['phraseanet.xsendfile-mapping'];
|
||||
|
||||
$output->writeln('<info>Apache XSendfile configuration</info>');
|
||||
$output->writeln('');
|
||||
$output->writeln('<IfModule mod_xsendfile.c>');
|
||||
$output->writeln(' <Files *>');
|
||||
$output->writeln(' XSendFile on');
|
||||
foreach ($this->container['phraseanet.xsendfile-mapping']->getMapping() as $entry) {
|
||||
$output->writeln(' XSendFilePath ' . $mapper->sanitizePath($entry['directory']));
|
||||
}
|
||||
$output->writeln(' </Files>');
|
||||
$output->writeln('</IfModule>');
|
||||
$output->writeln('');
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Command\Setup;
|
||||
|
||||
use Alchemy\Phrasea\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* This command dumps XSendFile Nginx configuration
|
||||
*/
|
||||
class XSendFileMappingNginxDumper extends Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('xsendfile:dump-nginx');
|
||||
|
||||
$this->setDescription('Dump xsendfile mapping for Nginx and Apache web server');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$mapper = $this->container['phraseanet.xsendfile-mapping'];
|
||||
$output->writeln('<info>Nginx XSendfile configuration</info>');
|
||||
$output->writeln('');
|
||||
foreach ($this->container['phraseanet.xsendfile-mapping']->getMapping() as $entry) {
|
||||
$output->writeln(' location ' . $mapper->sanitizeMountPoint($entry['mount-point']) . ' {');
|
||||
$output->writeln(' internal;');
|
||||
$output->writeln(' alias ' . $mapper->sanitizePath($entry['directory']));
|
||||
$output->writeln(' }');
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Core\Event\Subscriber;
|
||||
|
||||
use Silex\Application;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
|
||||
class XSendFileSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
private $app;
|
||||
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
KernelEvents::REQUEST => array('applyHeaders', 16),
|
||||
);
|
||||
}
|
||||
|
||||
public function applyHeaders(GetResponseEvent $event)
|
||||
{
|
||||
if ($this->app['phraseanet.configuration']['xsendfile']['enable']) {
|
||||
$request = $event->getRequest();
|
||||
$request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect');
|
||||
$request->headers->set('X-Accel-Mapping', (string) $this->app['phraseanet.xsendfile-mapping']);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\Core\Provider;
|
||||
|
||||
use Alchemy\Phrasea\XSendFile\Mapping;
|
||||
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||
use Silex\Application;
|
||||
use Silex\ServiceProviderInterface;
|
||||
|
||||
class XSendFileMappingServiceProvider implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Application $app)
|
||||
{
|
||||
if (!isset($app['xsendfile.mapping'])) {
|
||||
$app['xsendfile.mapping'] = array();
|
||||
}
|
||||
|
||||
if (!is_array($app['xsendfile.mapping'])) {
|
||||
throw new InvalidArgumentException('XSendFile mapping must be an array');
|
||||
}
|
||||
|
||||
$app['phraseanet.xsendfile-mapping'] = $app->share(function($app) {
|
||||
$mapping = array();
|
||||
foreach($app['xsendfile.mapping'] as $path => $mountPoint) {
|
||||
$mapping[] = array(
|
||||
'directory' => $path,
|
||||
'mount-point' => $mountPoint,
|
||||
);
|
||||
}
|
||||
|
||||
return Mapping::create($app, $mapping);
|
||||
});
|
||||
}
|
||||
|
||||
public function boot(Application $app)
|
||||
{
|
||||
}
|
||||
}
|
@@ -13,32 +13,17 @@ namespace Alchemy\Phrasea\Response;
|
||||
|
||||
use Alchemy\Phrasea\Response\DeliverDataInterface;
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Psr\Log\LoggerInterface,
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
class ServeFileResponseFactory implements DeliverDataInterface
|
||||
{
|
||||
private $xSendFileEnable = false;
|
||||
private $mappings;
|
||||
private $unicode;
|
||||
private $logger;
|
||||
|
||||
public function __construct($enableXSendFile, $xAccelMappings, \unicode $unicode, LoggerInterface $logger = null)
|
||||
public function __construct($enableXSendFile, \unicode $unicode)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
$this->xSendFileEnable = $enableXSendFile;
|
||||
|
||||
$mappings = array();
|
||||
|
||||
foreach ($xAccelMappings as $path => $mountPoint) {
|
||||
if (is_dir($path) && '' !== $mountPoint) {
|
||||
$mappings[$this->sanitizeXAccelPath($path)] = $this->sanitizeXAccelMountPoint($mountPoint);
|
||||
}
|
||||
}
|
||||
|
||||
$this->mappings = $mappings;
|
||||
$this->unicode = $unicode;
|
||||
}
|
||||
|
||||
@@ -49,12 +34,9 @@ class ServeFileResponseFactory implements DeliverDataInterface
|
||||
public static function create(Application $app)
|
||||
{
|
||||
return new self(
|
||||
$app['phraseanet.registry']->get('GV_modxsendfile'),
|
||||
array(
|
||||
$app['phraseanet.registry']->get('GV_X_Accel_Redirect') => $app['phraseanet.registry']->get('GV_X_Accel_Redirect_mount_point'),
|
||||
$app['root.path'] . '/tmp/download/' => '/download/',
|
||||
$app['root.path'] . '/tmp/lazaret/' => '/lazaret/'
|
||||
), new \unicode(), $app['logger']);
|
||||
$app['phraseanet.configuration']['xsendfile']['enable'],
|
||||
$app['unicode']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,11 +48,7 @@ class ServeFileResponseFactory implements DeliverDataInterface
|
||||
$response->setContentDisposition($disposition, $this->sanitizeFilename($filename), $this->sanitizeFilenameFallback($filename));
|
||||
|
||||
if ($this->isXSendFileEnable()) {
|
||||
if ($this->isMappedFile($file)) {
|
||||
$response->headers->set('X-Accel-Redirect', $this->xAccelRedirectMapping($file));
|
||||
} else if (null !== $this->logger) {
|
||||
$this->logger->warning(sprintf('%s is not located under a nginx xAccelPath'));
|
||||
}
|
||||
BinaryFileResponse::trustXSendfileTypeHeader();
|
||||
}
|
||||
|
||||
if (null !== $mimeType) {
|
||||
@@ -104,16 +82,6 @@ class ServeFileResponseFactory implements DeliverDataInterface
|
||||
return $this->xSendFileEnable;
|
||||
}
|
||||
|
||||
private function sanitizeXAccelPath($path)
|
||||
{
|
||||
return sprintf('%s/', rtrim($path, '/'));
|
||||
}
|
||||
|
||||
private function sanitizeXAccelMountPoint($mountPoint)
|
||||
{
|
||||
return sprintf('/%s/', rtrim(ltrim($mountPoint, '/'), '/'));
|
||||
}
|
||||
|
||||
private function sanitizeFilename($filename)
|
||||
{
|
||||
return str_replace(array('/', '\\'), '', $filename);
|
||||
@@ -123,20 +91,4 @@ class ServeFileResponseFactory implements DeliverDataInterface
|
||||
{
|
||||
return $this->unicode->remove_nonazAZ09($filename, true, true, true);
|
||||
}
|
||||
|
||||
private function xAccelRedirectMapping($file)
|
||||
{
|
||||
return str_replace(array_keys($this->mappings), array_values($this->mappings), $file);
|
||||
}
|
||||
|
||||
private function isMapped($file)
|
||||
{
|
||||
foreach (array_keys($this->mappings) as $path) {
|
||||
if (false !== strpos($file, $path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
91
lib/Alchemy/Phrasea/XSendFile/Mapping.php
Normal file
91
lib/Alchemy/Phrasea/XSendFile/Mapping.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Phraseanet
|
||||
*
|
||||
* (c) 2005-2013 Alchemy
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Alchemy\Phrasea\XSendFile;
|
||||
|
||||
use Alchemy\Phrasea\Application;
|
||||
use Alchemy\Phrasea\Exception\InvalidArgumentException;
|
||||
|
||||
class Mapping
|
||||
{
|
||||
private $mapping;
|
||||
|
||||
/**
|
||||
* @param array $mapping
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $mapping)
|
||||
{
|
||||
$this->validate($mapping);
|
||||
|
||||
$this->mapping = $mapping;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
$final = array();
|
||||
|
||||
foreach($this->mapping as $entry) {
|
||||
if (!is_dir($entry['directory']) || '' === $entry['mount-point']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$final[] = sprintf('%s=%s', $this->sanitizeMountPoint($entry['mount-point']), $this->sanitizePath(realpath($entry['directory'])));
|
||||
}
|
||||
|
||||
return implode(',', $final);
|
||||
}
|
||||
|
||||
public function getMapping()
|
||||
{
|
||||
return $this->mapping;
|
||||
}
|
||||
|
||||
public static function create(Application $app, array $mapping = array())
|
||||
{
|
||||
if (isset($app['phraseanet.configuration']['xsendfile']['mapping'])) {
|
||||
$confMapping = $app['phraseanet.configuration']['xsendfile']['mapping'];
|
||||
|
||||
if (!is_array($confMapping)) {
|
||||
throw new InvalidArgumentException('XSendFile mapping configuration must be an array');
|
||||
}
|
||||
|
||||
foreach($confMapping as $entry) {
|
||||
$mapping[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
return new Mapping($mapping);
|
||||
}
|
||||
|
||||
public function sanitizePath($path)
|
||||
{
|
||||
return sprintf('/%s', rtrim(ltrim($path, '/'),'/'));
|
||||
}
|
||||
|
||||
public function sanitizeMountPoint($mountPoint)
|
||||
{
|
||||
return sprintf('/%s', rtrim(ltrim($mountPoint, '/'), '/'));
|
||||
}
|
||||
|
||||
private function validate(array $mapping)
|
||||
{
|
||||
foreach($mapping as $entry) {
|
||||
if (!is_array($entry)) {
|
||||
throw new \InvalidArgumentException('XSendFile mapping entry must be an array');
|
||||
}
|
||||
|
||||
if (!isset($entry['directory']) && !isset($entry['mount-point'])) {
|
||||
throw new \InvalidArgumentException('XSendFile mapping entry must contain at least two keys "directory" and "mounbt-point"');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user