diff --git a/bin/console b/bin/console index b47b654126..8a73b4088c 100755 --- a/bin/console +++ b/bin/console @@ -28,8 +28,7 @@ use Alchemy\Phrasea\CLI; use Alchemy\Phrasea\Command\Plugin\AddPlugin; use Alchemy\Phrasea\Command\Plugin\RemovePlugin; use Alchemy\Phrasea\Command\CheckConfig; -use Alchemy\Phrasea\Command\Setup\XSendFileMappingNginxDumper; -use Alchemy\Phrasea\Command\Setup\XSendFileMappingApacheDumper; +use Alchemy\Phrasea\Command\Setup\XSendFileMappingDumper; require_once __DIR__ . '/../lib/autoload.php'; @@ -98,8 +97,7 @@ try { $app->command(new AddPlugin()); $app->command(new RemovePlugin()); $app->command(new Configuration()); - $app->command(new XSendFileMappingNginxDumper()); - $app->command(new XSendFileMappingApacheDumper()); + $app->command(new XSendFileMappingDumper()); $result_code = is_int($app->run()) ? : 1; } catch (\Exception $e) { diff --git a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingApacheDumper.php b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingApacheDumper.php deleted file mode 100644 index 96496136c2..0000000000 --- a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingApacheDumper.php +++ /dev/null @@ -1,50 +0,0 @@ -setDescription('Dump XSendFile mapping for Apache web server'); - } - - /** - * {@inheritdoc} - */ - protected function doDump(InputInterface $input, OutputInterface $output) - { - $mapper = $this->container['phraseanet.xsendfile-mapping']; - - $output->writeln('Apache XSendfile configuration'); - $output->writeln(''); - $output->writeln(''); - $output->writeln(' '); - $output->writeln(' XSendFile on'); - foreach ($this->container['phraseanet.xsendfile-mapping']->getMapping() as $entry) { - $output->writeln(' XSendFilePath ' . $mapper->sanitizePath($entry['directory'])); - } - $output->writeln(' '); - $output->writeln(''); - $output->writeln(''); - - return 0; - } -} diff --git a/lib/Alchemy/Phrasea/Command/Setup/AbstractXSendFileMappingDumper.php b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingDumper.php similarity index 55% rename from lib/Alchemy/Phrasea/Command/Setup/AbstractXSendFileMappingDumper.php rename to lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingDumper.php index ef44829d4f..aa61c2a83e 100644 --- a/lib/Alchemy/Phrasea/Command/Setup/AbstractXSendFileMappingDumper.php +++ b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingDumper.php @@ -12,35 +12,45 @@ namespace Alchemy\Phrasea\Command\Setup; use Alchemy\Phrasea\Command\Command; +use Alchemy\Phrasea\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -abstract class AbstractXSendFileMappingDumper extends Command +class XSendFileMappingDumper extends Command { + public function __construct($name = null) { + parent::__construct('xsendfile:configuration-dumper'); + } + /** * {@inheritdoc} */ protected function doExecute(InputInterface $input, OutputInterface $output) { - $configuration = 0; - $output->writeln(''); - if ($this->container['phraseanet.file-serve']->isXSendFileEnable()) { - $output->writeln('XSendFile support is enabled'); - } else { + + if (!$this->container['phraseanet.xsendfile-factory']->isXSendFileModeEnabled()) { $output->writeln('XSendFile support is disabled'); - $configuration++; + + return 1; } - if (2 < count($this->container['phraseanet.xsendfile-mapping']->getMapping())) { + + $output->writeln('XSendFile support is enabled'); + + try { + $configuration = $this->container['phraseanet.xsendfile-factory']->getMode(true)->getVirtualHostConfiguration(); $output->writeln('XSendFile configuration seems OK'); - } else { + $output->writeln($configuration); + + return 0; + } catch (RuntimeException $e) { $output->writeln('XSendFile configuration seems invalid'); - $configuration++; + + return 1; } + $output->writeln(''); - return $configuration + $this->doDump($input, $output); + return 0; } - - abstract protected function doDump(InputInterface $input, OutputInterface $output); } diff --git a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingNginxDumper.php b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingNginxDumper.php deleted file mode 100644 index 1b949c5be0..0000000000 --- a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingNginxDumper.php +++ /dev/null @@ -1,49 +0,0 @@ -setDescription('Dump xsendfile mapping for Nginx web server'); - } - - /** - * {@inheritdoc} - */ - protected function doDump(InputInterface $input, OutputInterface $output) - { - $mapper = $this->container['phraseanet.xsendfile-mapping']; - $output->writeln('Nginx XSendfile configuration'); - $output->writeln(''); - foreach ($this->container['phraseanet.xsendfile-mapping']->getMapping() as $entry) { - $output->writeln(' location ' . $mapper->sanitizeMountPoint($entry['mount-point']) . ' {'); - $output->writeln(' internal;'); - $output->writeln(' add_header Etag $upstream_http_etag;'); - $output->writeln(' add_header Link $upstream_http_link;'); - $output->writeln(' alias ' . $mapper->sanitizePath($entry['directory']) . ';'); - $output->writeln(' }'); - $output->writeln(''); - } - - return 0; - } -} diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/XSendFileSubscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/XSendFileSubscriber.php index 8b9381f82a..d6aff6bc14 100644 --- a/lib/Alchemy/Phrasea/Core/Event/Subscriber/XSendFileSubscriber.php +++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/XSendFileSubscriber.php @@ -15,6 +15,7 @@ use Silex\Application; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpFoundation\BinaryFileResponse; class XSendFileSubscriber implements EventSubscriberInterface { @@ -34,10 +35,9 @@ class XSendFileSubscriber implements EventSubscriberInterface public function applyHeaders(GetResponseEvent $event) { - if ($this->app['phraseanet.configuration']['xsendfile']['enabled']) { - $request = $event->getRequest(); - $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect'); - $request->headers->set('X-Accel-Mapping', (string) $this->app['phraseanet.xsendfile-mapping']); + if ($this->app['phraseanet.xsendfile-factory']->isXSendFileModeEnabled()) { + BinaryFileResponse::trustXSendfileTypeHeader(); + $this->app['phraseanet.xsendfile-factory']->getMode()->setHeaders($event->getRequest()); } } } diff --git a/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php index 0c288b197f..3125e12ff2 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php @@ -11,11 +11,11 @@ namespace Alchemy\Phrasea\Core\Provider; +use Alchemy\Phrasea\Core\Event\Subscriber\XSendFileSubscriber; +use Alchemy\Phrasea\Http\ServeFileResponseFactory; +use Alchemy\Phrasea\Http\XSendFile\XSendFileFactory; use Silex\Application; use Silex\ServiceProviderInterface; -use Alchemy\Phrasea\Http\ServeFileResponseFactory; -use Alchemy\Phrasea\Http\XsendfileMapping; -use Alchemy\Phrasea\Core\Event\Subscriber\XSendFileSubscriber; class FileServeServiceProvider implements ServiceProviderInterface { @@ -24,27 +24,8 @@ class FileServeServiceProvider implements ServiceProviderInterface */ public function register(Application $app) { - $app['xsendfile.mapping'] = $app->share(function(Application $app) { - $mapping = array(); - - if (isset($app['phraseanet.configuration']['xsendfile']['mapping'])) { - $mapping = $app['phraseanet.configuration']['xsendfile']['mapping']; - } - - $mapping[] = array( - 'directory' => $app['root.path'] . '/tmp/download/', - 'mount-point' => '/download/', - ); - $mapping[] = array( - 'directory' => $app['root.path'] . '/tmp/lazaret/', - 'mount-point' => '/lazaret/', - ); - - return $mapping; - }); - - $app['phraseanet.xsendfile-mapping'] = $app->share(function($app) { - return new XsendfileMapping($app['xsendfile.mapping']); + $app['phraseanet.xsendfile-factory'] = $app->share(function($app) { + return XSendFileFactory::create($app); }); $app['phraseanet.file-serve'] = $app->share(function (Application $app) { diff --git a/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php b/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php index 11576cc9c3..a1233950fe 100644 --- a/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php +++ b/lib/Alchemy/Phrasea/Http/DeliverDataInterface.php @@ -24,7 +24,7 @@ interface DeliverDataInterface * @param string $file * @param string $filename * @param string $disposition - * @param string|null $mimetype + * @param string|null $mimeType * @param integer $cacheDuration */ public function deliverFile($file, $filename = null, $disposition = self::DISPOSITION_INLINE, $mimeType = null, $cacheDuration = 3600); diff --git a/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php b/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php index 94aaf5f90b..fd709afbdd 100644 --- a/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php +++ b/lib/Alchemy/Phrasea/Http/ServeFileResponseFactory.php @@ -18,17 +18,11 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse; class ServeFileResponseFactory implements DeliverDataInterface { - private $xSendFileEnable = false; private $unicode; - public function __construct($enableXSendFile, \unicode $unicode) + public function __construct(\unicode $unicode) { - $this->xSendFileEnable = (Boolean) $enableXSendFile; $this->unicode = $unicode; - - if ($this->xSendFileEnable) { - BinaryFileResponse::trustXSendfileTypeHeader(); - } } /** @@ -38,7 +32,6 @@ class ServeFileResponseFactory implements DeliverDataInterface public static function create(Application $app) { return new self( - $app['phraseanet.configuration']['xsendfile']['enabled'], $app['unicode'] ); } @@ -77,11 +70,6 @@ class ServeFileResponseFactory implements DeliverDataInterface return $response; } - public function isXSendFileEnable() - { - return $this->xSendFileEnable; - } - private function sanitizeFilename($filename) { return str_replace(array('/', '\\'), '', $filename); diff --git a/lib/Alchemy/Phrasea/Http/XSendFile/AbstractXSendFileMode.php b/lib/Alchemy/Phrasea/Http/XSendFile/AbstractXSendFileMode.php new file mode 100644 index 0000000000..cfc09ffc92 --- /dev/null +++ b/lib/Alchemy/Phrasea/Http/XSendFile/AbstractXSendFileMode.php @@ -0,0 +1,68 @@ +setMapping($mapping); + } + + /** + * @return array + */ + public function getMapping() + { + return $this->mapping; + } + + /** + * @params array $mapping + * + * @throws InvalidArgumentException if mapping is invalid; + */ + abstract public function setMapping(array $mapping); + + /** + * Sanitizes path directory. + * + * @param string $path + * + * @return string + */ + protected function sanitizePath($path) + { + return sprintf('/%s', trim($path, '/')); + } + + /** + * Sanitizes a mount point. + * + * @param string $mountPoint + * + * @return string + */ + protected function sanitizeMountPoint($mountPoint) + { + return sprintf('/%s', trim($mountPoint, '/')); + } +} diff --git a/lib/Alchemy/Phrasea/Http/XSendFile/ApacheMode.php b/lib/Alchemy/Phrasea/Http/XSendFile/ApacheMode.php new file mode 100644 index 0000000000..1102205288 --- /dev/null +++ b/lib/Alchemy/Phrasea/Http/XSendFile/ApacheMode.php @@ -0,0 +1,74 @@ +headers->add(array( + 'X-Sendfile-Type' => 'X-SendFile', + )); + } + + /** + * {@inheritdoc} + */ + public function setMapping(array $mapping) + { + $final = array(); + + foreach ($mapping as $entry) { + if (!is_array($entry)) { + throw new InvalidArgumentException('XSendFile mapping entry must be an array'); + } + + if (!isset($entry['directory'])) { + throw new InvalidArgumentException('XSendFile mapping entry must contain at least a "directory" key'); + } + + if (false === is_dir(trim($entry['directory']))) { + continue; + } + + $final[] = array( + 'directory' => $this->sanitizePath(realpath($entry['directory'])) + ); + } + + $this->mapping = $final; + } + + /** + * {@inheritdoc} + */ + public function getVirtualHostConfiguration() + { + $output = "\n"; + $output .= "\n"; + $output .= " \n"; + $output .= " XSendFile on\n"; + foreach ($this->mapping as $entry) { + $output .= ' XSendFilePath ' . $entry['directory'] . "\n"; + } + $output .= " \n"; + $output .= "\n"; + + return $output; + } +} diff --git a/lib/Alchemy/Phrasea/Http/XSendFile/ModeInterface.php b/lib/Alchemy/Phrasea/Http/XSendFile/ModeInterface.php new file mode 100644 index 0000000000..0ba0d2638d --- /dev/null +++ b/lib/Alchemy/Phrasea/Http/XSendFile/ModeInterface.php @@ -0,0 +1,31 @@ +mapping as $entry) { + $xAccelMapping[] = sprintf('%s=%s', $entry['mount-point'], $entry['directory']); + } + + if (count($xAccelMapping) > 0 ) { + $request->headers->add(array( + 'X-Sendfile-Type' => 'X-Accel-Redirect', + 'X-Accel-Mapping' => implode(',', $xAccelMapping), + )); + } + } + + /** + * {@inheritdoc} + */ + public function setMapping(array $mapping) + { + $final = array(); + + foreach ($mapping as $entry) { + if (!is_array($entry)) { + throw new InvalidArgumentException('XSendFile mapping entry must be an array'); + } + + if (!isset($entry['directory'])) { + throw new InvalidArgumentException('XSendFile mapping entry must contain at least a "directory" key'); + } + + if (!isset($entry['mount-point'])) { + throw new InvalidArgumentException('XSendFile mapping entry must contain at least a "mount-point" key'); + } + + if (false === is_dir(trim($entry['directory'])) || '' === trim($entry['mount-point'])) { + continue; + } + + $final[] = array( + 'directory' => $this->sanitizePath(realpath($entry['directory'])), + 'mount-point' => $this->sanitizeMountPoint($entry['mount-point']) + ); + } + + $this->mapping = $final; + } + + /** + * {@inheritdoc} + */ + public function getVirtualHostConfiguration() + { + $output = "\n"; + foreach ($this->mapping as $entry) { + $output .= " location " . $entry['mount-point']. " {\n"; + $output .= " internal;\n"; + $output .= " add_header Etag \$upstream_http_etag;\n"; + $output .= " add_header Link \$upstream_http_link;\n"; + $output .= " alias " . $entry['directory'] . ";\n"; + $output .= " }\n"; + } + + return $output; + } +} diff --git a/lib/Alchemy/Phrasea/Http/XSendFile/NullMode.php b/lib/Alchemy/Phrasea/Http/XSendFile/NullMode.php new file mode 100644 index 0000000000..b8e3bea3dc --- /dev/null +++ b/lib/Alchemy/Phrasea/Http/XSendFile/NullMode.php @@ -0,0 +1,32 @@ +logger = $logger; + $this->enabled = (Boolean) $enabled; + $this->type = strtolower($type); + $this->mapping = $mapping; + } + + /** + * Creates a new instance of XSendFile Factory according to the application + * configuration. + * + * @param Application $app + * @return XSendFileFactory + */ + public static function create(Application $app) + { + $conf = $app['phraseanet.configuration']['xsendfile']; + + $mapping = array(); + + if (isset($conf['mapping'])) { + $mapping = $conf['mapping']; + } + + $mapping[] = array( + 'directory' => $app['root.path'] . '/tmp/download/', + 'mount-point' => '/download/', + ); + + $mapping[] = array( + 'directory' => $app['root.path'] . '/tmp/lazaret/', + 'mount-point' => '/lazaret/', + ); + + return new self($app['monolog'], $conf['enabled'], $conf['type'], $mapping); + } + + /** + * Returns a new instance of XSendFileModeInterface. + * + * @return null|ModeInterface + * + * @throws InvalidArgumentException if mode type is unknown + */ + public function getMode($throwException = false) + { + if (false === $this->enabled) { + return new NullMode(); + } + + switch ($this->type) { + case 'nginx': + case 'sendfile': + case 'xaccel': + case 'xaccelredirect': + case 'x-accel': + case 'x-accel-redirect': + if (2 >= count($this->mapping)) { + $this->logger->error('Invalid xsendfile mapping configuration.'); + if ($throwException) { + throw new RuntimeException('Mapping is not set up.'); + } + } + + return new NginxMode($this->mapping); + case 'apache': + case 'apache2': + case 'xsendfile': + return new ApacheMode($this->mapping); + default: + $this->logger->error('Invalid xsendfile type configuration.'); + if ($throwException) { + throw new InvalidArgumentException(sprintf( + 'Invalid xsendfile type value "%s"', + $this->type + )); + } + + return new NullMode(); + } + } + + /** + * @return Boolean + */ + public function isXSendFileModeEnabled() + { + return $this->enabled; + } +} diff --git a/lib/Alchemy/Phrasea/Http/XsendfileMapping.php b/lib/Alchemy/Phrasea/Http/XsendfileMapping.php deleted file mode 100644 index 094ee5d21f..0000000000 --- a/lib/Alchemy/Phrasea/Http/XsendfileMapping.php +++ /dev/null @@ -1,85 +0,0 @@ -validate($mapping); - - $final = array(); - - foreach($mapping as $entry) { - if (!is_dir(trim($entry['directory'])) || '' === trim($entry['mount-point'])) { - continue; - } - - $entry = array( - 'directory' => $this->sanitizePath(realpath($entry['directory'])), - 'mount-point' => $this->sanitizeMountPoint($entry['mount-point']), - ); - - $final[] = $entry; - } - - $this->mapping = $final; - } - - public function __toString() - { - $final = array(); - - foreach($this->mapping as $entry) { - $final[] = sprintf('%s=%s', $entry['mount-point'], $entry['directory']); - } - - return implode(',', $final); - } - - public function getMapping() - { - return $this->mapping; - } - - public function sanitizePath($path) - { - return sprintf('/%s', trim($path, '/')); - } - - public function sanitizeMountPoint($mountPoint) - { - return sprintf('/%s', trim($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 "mount-point"'); - } - } - } -} diff --git a/lib/classes/patch/3813.php b/lib/classes/patch/3813.php index f458cf2200..f39f3628e2 100644 --- a/lib/classes/patch/3813.php +++ b/lib/classes/patch/3813.php @@ -56,6 +56,7 @@ class patch_3813 implements patchInterface ->getConfig(); $config['xsendfile']['enabled'] = (Boolean) $app['phraseanet.registry']->get('GV_modxsendfile', false); + $config['xsendfile']['type'] = $config['xsendfile']['enabled'] ? 'nginx' : ''; if (null !== $xsendfilePath && null !== $xsendfileMountPoint) { $config['xsendfile']['mapping'] = array(array( diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml index fa1365a9c7..faebf9dd06 100644 --- a/lib/conf.d/configuration.yml +++ b/lib/conf.d/configuration.yml @@ -135,6 +135,7 @@ registration-fields: required: true xsendfile: enabled: false + type: nginx|apache2 mapping: - directory: '' diff --git a/templates/web/admin/editusers.html.twig b/templates/web/admin/editusers.html.twig index 769cd92607..27fc2ec927 100644 --- a/templates/web/admin/editusers.html.twig +++ b/templates/web/admin/editusers.html.twig @@ -145,7 +145,7 @@ {% endfor %} - + @@ -565,7 +565,7 @@ {% endif %}
- + {% trans 'boutton::retour' %}
diff --git a/templates/web/admin/search-engine/sphinx-search.html.twig b/templates/web/admin/search-engine/sphinx-search.html.twig index 324313e4b8..f6b290c7b0 100644 --- a/templates/web/admin/search-engine/sphinx-search.html.twig +++ b/templates/web/admin/search-engine/sphinx-search.html.twig @@ -2,14 +2,14 @@
{% trans 'Sphinx Search connection configuration' %}
- +
{% trans 'Sphinx Search server' %}
{% trans 'Sphinx Search RealTime server' %}
- +
{% trans 'Charset to use for indexation' %}
{% for charset, charsetObject in charsets %} {{ charsetObject.get_name() }} @@ -20,7 +20,7 @@ {{ field }} {% endfor %} - +
\ No newline at end of file diff --git a/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-setup.yml b/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-setup.yml index c449b45cfd..55b4553acd 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-setup.yml +++ b/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration-setup.yml @@ -116,6 +116,7 @@ registration-fields: required: true xsendfile: enabled: false + type: '' mapping: - directory: '' diff --git a/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration.yml b/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration.yml index c449b45cfd..55b4553acd 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration.yml +++ b/tests/Alchemy/Tests/Phrasea/Core/Configuration/Fixtures/configuration.yml @@ -116,6 +116,7 @@ registration-fields: required: true xsendfile: enabled: false + type: '' mapping: - directory: '' diff --git a/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-debugger.yml b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-debugger.yml index d29c6648a9..85d4fe3e4a 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-debugger.yml +++ b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-debugger.yml @@ -141,6 +141,7 @@ registration-fields: required: true xsendfile: enabled: false + type: '' mapping: - directory: '' diff --git a/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml index 4a9cd110d4..42f4b2f2ba 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml +++ b/tests/Alchemy/Tests/Phrasea/Core/Event/Subscriber/Fixtures/configuration-maintenance.yml @@ -141,6 +141,7 @@ registration-fields: required: true xsendfile: enabled: false + type: '' mapping: - directory: '' diff --git a/tests/Alchemy/Tests/Phrasea/Core/Provider/FileServeServiceProviderTest.php b/tests/Alchemy/Tests/Phrasea/Core/Provider/FileServeServiceProviderTest.php index 331603bbce..53d71d627e 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Provider/FileServeServiceProviderTest.php +++ b/tests/Alchemy/Tests/Phrasea/Core/Provider/FileServeServiceProviderTest.php @@ -21,32 +21,24 @@ class FileServeServiceProviderTest extends ServiceProviderTestCase ), array( 'Alchemy\Phrasea\Core\Provider\FileServeServiceProvider', - 'phraseanet.xsendfile-mapping', - 'Alchemy\Phrasea\Http\XsendfileMapping' + 'phraseanet.xsendfile-factory', + 'Alchemy\Phrasea\Http\XSendFile\XSendFileFactory' ), ); } public function testMapping() { - $app = new Application(); - + $app = clone self::$DI['app']; $app['root.path'] = __DIR__ . '/../../../../../..'; $app->register(new ConfigurationServiceProvider()); $app->register(new FileServeServiceProvider()); $app['phraseanet.configuration.config-path'] = __DIR__ . '/fixtures/config-mapping.yml'; $app['phraseanet.configuration.config-compiled-path'] = __DIR__ . '/fixtures/config-mapping.php'; - $this->assertEquals(array(array( - 'directory' => '/tmp', - 'mount-point' => 'mount', - ),array( - 'directory' => __DIR__ . '/../../../../../../tmp/download/', - 'mount-point' => '/download/', - ),array( - 'directory' => __DIR__ . '/../../../../../../tmp/lazaret/', - 'mount-point' => '/lazaret/', - )), $app['xsendfile.mapping']); + $this->assertInstanceOf('Alchemy\Phrasea\Http\XSendFile\NginxMode', $app['phraseanet.xsendfile-factory']->getMode()); + $this->assertEquals(3, count($app['phraseanet.xsendfile-factory']->getMode()->getMapping())); unlink($app['phraseanet.configuration.config-compiled-path']); + unset($app); } } diff --git a/tests/Alchemy/Tests/Phrasea/Core/Provider/fixtures/config-mapping.yml b/tests/Alchemy/Tests/Phrasea/Core/Provider/fixtures/config-mapping.yml index a671526952..707126a9a9 100644 --- a/tests/Alchemy/Tests/Phrasea/Core/Provider/fixtures/config-mapping.yml +++ b/tests/Alchemy/Tests/Phrasea/Core/Provider/fixtures/config-mapping.yml @@ -1,5 +1,6 @@ xsendfile: - enabled: false + enabled: true + type: 'nginx' mapping: - directory: '/tmp' diff --git a/tests/Alchemy/Tests/Phrasea/Http/ServeFileResponseFactoryTest.php b/tests/Alchemy/Tests/Phrasea/Http/ServeFileResponseFactoryTest.php index ef11c49119..a355a61e66 100644 --- a/tests/Alchemy/Tests/Phrasea/Http/ServeFileResponseFactoryTest.php +++ b/tests/Alchemy/Tests/Phrasea/Http/ServeFileResponseFactoryTest.php @@ -3,16 +3,23 @@ namespace Alchemy\Tests\Phrasea\Http; use Alchemy\Phrasea\Http\ServeFileResponseFactory; -use Alchemy\Phrasea\Http\XsendfileMapping; +use Alchemy\Phrasea\Http\XSendFile\NginxMode; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract { protected $factory; + public function testDeliverFileFactoryCreation() + { + $factory = ServeFileResponseFactory::create(self::$DI['app']); + $this->assertInstanceOf('Alchemy\Phrasea\Http\ServeFileResponseFactory', $factory); + } + public function testDeliverFile() { - $this->factory = new ServeFileResponseFactory(false, new \unicode()); + $this->factory = new ServeFileResponseFactory(new \unicode()); $response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg'); @@ -25,7 +32,7 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract public function testDeliverFileWithDuration() { - $this->factory = new ServeFileResponseFactory(false, new \unicode()); + $this->factory = new ServeFileResponseFactory(new \unicode()); $response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'hello', 'attachment', 'application/json', 23456); @@ -35,7 +42,7 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract public function testDeliverFileWithFilename() { - $this->factory = new ServeFileResponseFactory(false, new \unicode()); + $this->factory = new ServeFileResponseFactory(new \unicode()); $response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg'); @@ -45,7 +52,7 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract public function testDeliverFileWithFilenameAndDisposition() { - $this->factory = new ServeFileResponseFactory(false, new \unicode()); + $this->factory = new ServeFileResponseFactory(new \unicode()); $response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg', 'attachment'); @@ -55,15 +62,18 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract public function testDeliverFileWithFilenameAndDispositionAndXSendFile() { - $this->factory = new ServeFileResponseFactory(true, new \unicode()); - $request = Request::create('/'); - $request->headers->set('X-SendFile-Type', 'X-Accel-Redirect'); - $request->headers->set('X-Accel-Mapping', (string) new XsendfileMapping(array( + BinaryFileResponse::trustXSendfileTypeHeader(); + $this->factory = new ServeFileResponseFactory(new \unicode()); + $mode = new NginxMode( array( - 'directory' => __DIR__ . '/../../../../files/', - 'mount-point' => '/protected/' + array( + 'directory' => __DIR__ . '/../../../../files/', + 'mount-point' => '/protected/' + ) ) - ))); + ); + $request = Request::create('/'); + $mode->setHeaders($request); $response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg', 'attachment'); $response->prepare($request); @@ -75,15 +85,18 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract public function testDeliverFileWithFilenameAndDispositionAndXSendFileAndNoTrailingSlashes() { - $this->factory = new ServeFileResponseFactory(true, new \unicode()); - $request = Request::create('/'); - $request->headers->set('X-SendFile-Type', 'X-Accel-Redirect'); - $request->headers->set('X-Accel-Mapping', (string) new XsendfileMapping(array( + BinaryFileResponse::trustXSendfileTypeHeader(); + $this->factory = new ServeFileResponseFactory(new \unicode()); + $mode = new NginxMode( array( - 'directory' => __DIR__ . '/../../../../files/', - 'mount-point' => '/protected/' + array( + 'directory' => __DIR__ . '/../../../../files', + 'mount-point' => '/protected' + ) ) - ))); + ); + $request = Request::create('/'); + $mode->setHeaders($request); $response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg', 'attachment'); $response->prepare($request); @@ -98,22 +111,26 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract */ public function testDeliverUnexistingFile() { - $this->factory = new ServeFileResponseFactory(true, new \unicode()); + BinaryFileResponse::trustXSendfileTypeHeader(); + $this->factory = new ServeFileResponseFactory(new \unicode()); $this->factory->deliverFile(__DIR__ . '/../../../../files/does_not_exists.jpg', 'toto.jpg', 'attachment'); } public function testDeliverFileWithFilenameAndDispositionAndXSendFileButFileNotInXAccelMapping() { - $this->factory = new ServeFileResponseFactory(true, new \unicode()); - $request = Request::create('/'); - $request->headers->set('X-SendFile-Type', 'X-Accel-Redirect'); - $request->headers->set('X-Accel-Mapping', (string) new XsendfileMapping(array( + BinaryFileResponse::trustXSendfileTypeHeader(); + $this->factory = new ServeFileResponseFactory(new \unicode()); + $mode = new NginxMode( array( - 'directory' => __DIR__ . '/../../../../files/', - 'mount-point' => '/protected/' + array( + 'directory' => __DIR__ . '/../../../../files/', + 'mount-point' => '/protected/' + ) ) - ))); + ); + $request = Request::create('/'); + $mode->setHeaders($request); $file = __DIR__ . '/../../../../classes/PhraseanetPHPUnitAbstract.php'; @@ -127,7 +144,7 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract public function testDeliverDatas() { - $this->factory = new ServeFileResponseFactory(false, new \unicode()); + $this->factory = new ServeFileResponseFactory(new \unicode()); $data = 'Sex,Name,Birthday M,Alphonse,1932 diff --git a/tests/Alchemy/Tests/Phrasea/Http/XSendFile/ApacheModeTest.php b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/ApacheModeTest.php new file mode 100644 index 0000000000..7941d732a6 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/ApacheModeTest.php @@ -0,0 +1,47 @@ + __DIR__ ))); + $conf = $mode->getVirtualHostConfiguration(); + $this->assertRegExp('#'.__DIR__ . '#', $conf); + } + + public function testSetValidHeaders() + { + $request = Request::create('/'); + $mode = new ApacheMode(array(array('directory' => __DIR__ ))); + $mode->setHeaders($request); + $this->assertArrayHasKey('x-sendfile-type', $request->headers->all()); + } + + public function testUnextingDirectoryMapping() + { + $mode = new ApacheMode(array(array('directory' => __DIR__ . '/Unknown/Dir'))); + $this->assertEquals(array(), $mode->getMapping()); + } + + /** + * @dataProvider provideMappings + * @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException + */ + public function testInvalidMapping($mapping) + { + new ApacheMode($mapping); + } + + public function provideMappings() + { + return array( + array(array(array('wrong-key' => __DIR__))), + array(array('not-an-array')), + ); + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Http/XSendFile/NginxModeTest.php b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/NginxModeTest.php new file mode 100644 index 0000000000..38685e359f --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/NginxModeTest.php @@ -0,0 +1,82 @@ + __DIR__, 'mount-point' => '/download'))); + $conf = $mode->getVirtualHostConfiguration(); + $this->assertRegExp('#'.__DIR__ . '#', $conf); + } + + public function testSetValidHeaders() + { + $request = Request::create('/'); + $mode = new NginxMode(array(array('directory' => __DIR__, 'mount-point' => '/download'))); + $mode->setHeaders($request); + $this->assertArrayHasKey('x-sendfile-type', $request->headers->all()); + $this->assertArrayHasKey('x-accel-mapping', $request->headers->all()); + } + + public function testSetValidMultiHeaders() + { + $protected = __DIR__ . '/../../../../../files/'; + $upload = __DIR__ . '/../../../../../'; + + $request = Request::create('/'); + $mode = new NginxMode(array( + array( + 'directory' => $protected, + 'mount-point' => '/protected/' + ), + array( + 'directory' => $upload, + 'mount-point' => '/uploads/' + ), + )); + $mode->setHeaders($request); + $this->assertArrayHasKey('x-sendfile-type', $request->headers->all()); + $this->assertArrayHasKey('x-accel-mapping', $request->headers->all()); + $this->assertEquals('/protected='.realpath($protected).',/uploads='.realpath($upload), $request->headers->get('X-Accel-Mapping')); + } + + public function testSetInvalidHeaders() + { + $request = Request::create('/'); + $mode = new NginxMode(array(array('directory' => __DIR__ . '/Unknown/Dir', 'mount-point' => '/download'))); + $mode->setHeaders($request); + $this->assertArrayNotHasKey('x-sendfile-type', $request->headers->all()); + $this->assertArrayNotHasKey('x-accel-mapping', $request->headers->all()); + } + + public function testUnextingDirectoryMapping() + { + $mode = new NginxMode(array(array('directory' => __DIR__ . '/Unknown/Dir', 'mount-point' => '/download'))); + $this->assertEquals(array(), $mode->getMapping()); + } + + /** + * @dataProvider provideMappings + * @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException + */ + public function testInvalidMapping($mapping) + { + new NginxMode($mapping); + } + + public function provideMappings() + { + return array( + array(array(array('Directory' => __DIR__))), + array(array(array('wrong-key' => __DIR__, 'mount-point' => '/'))), + array(array(array('directory' => __DIR__, 'wrong-key' => '/'))), + array(array('not-an-array')), + ); + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Http/XSendFile/NullModeTest.php b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/NullModeTest.php new file mode 100644 index 0000000000..fee4729fc2 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/NullModeTest.php @@ -0,0 +1,25 @@ +getVirtualHostConfiguration(); + $this->assertSame("\n", $conf); + } + + public function testSetHeaders() + { + $mode = new NullMode(); + $request = Request::create('/'); + $before = (string) $request->headers; + $mode->setHeaders($request); + $this->assertSame($before, (string) $request->headers); + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Http/XSendFile/XSendFileFactoryTest.php b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/XSendFileFactoryTest.php new file mode 100644 index 0000000000..39beb2f9f4 --- /dev/null +++ b/tests/Alchemy/Tests/Phrasea/Http/XSendFile/XSendFileFactoryTest.php @@ -0,0 +1,132 @@ +assertInstanceOf('Alchemy\Phrasea\Http\XSendFile\XSendFileFactory', $factory); + } + + public function testFactoryWithXsendFileEnable() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $factory = new XSendFileFactory($logger, true, 'nginx', $this->getNginxMapping()); + $this->assertInstanceOf('Alchemy\Phrasea\Http\XSendFile\ModeInterface', $factory->getMode()); + } + + public function testFactoryWithXsendFileDisabled() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $factory = new XSendFileFactory($logger, false, 'nginx',$this->getNginxMapping()); + $this->assertInstanceOf('Alchemy\Phrasea\Http\XSendFile\NullMode', $factory->getMode()); + $this->assertFalse($factory->isXSendFileModeEnabled()); + } + + /** + * @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException + */ + public function testFactoryWithWrongTypeThrowsAnExceptionIfRequired() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $factory = new XSendFileFactory($logger, true, 'wrong-type', $this->getNginxMapping()); + $factory->getMode(true); + } + + public function testFactoryWithWrongTypeDoesNotThrowsAnExceptio() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $logger->expects($this->once()) + ->method('error') + ->with($this->isType('string')); + + $factory = new XSendFileFactory($logger, true, 'wrong-type', $this->getNginxMapping()); + $this->assertInstanceOf('Alchemy\Phrasea\Http\XSendFile\NullMode', $factory->getMode(false)); + } + + /** + * @dataProvider provideTypes + */ + public function testFactoryType($type, $mapping, $classmode) + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $factory = new XSendFileFactory($logger, true, $type, $mapping); + $this->assertInstanceOf($classmode, $factory->getMode()); + } + + public function provideTypes() + { + return array( + array('apache', $this->getApacheMapping(), 'Alchemy\Phrasea\Http\XSendFile\ApacheMode'), + array('apache2', $this->getApacheMapping(), 'Alchemy\Phrasea\Http\XSendFile\ApacheMode'), + array('xsendfile', $this->getApacheMapping(), 'Alchemy\Phrasea\Http\XSendFile\ApacheMode'), + array('nginx',$this->getNginxMapping(), 'Alchemy\Phrasea\Http\XSendFile\NginxMode'), + array('sendfile',$this->getNginxMapping(), 'Alchemy\Phrasea\Http\XSendFile\NginxMode'), + array('xaccel',$this->getNginxMapping(), 'Alchemy\Phrasea\Http\XSendFile\NginxMode'), + array('xaccelredirect',$this->getNginxMapping(), 'Alchemy\Phrasea\Http\XSendFile\NginxMode'), + array('x-accel',$this->getNginxMapping(), 'Alchemy\Phrasea\Http\XSendFile\NginxMode'), + array('x-accel-redirect',$this->getNginxMapping(), 'Alchemy\Phrasea\Http\XSendFile\NginxMode'), + ); + } + + public function testInvalidMappingThrowsAnExceptionIfRequired() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $logger->expects($this->once()) + ->method('error') + ->with($this->isType('string')); + + $factory = new XSendFileFactory($logger, true, 'nginx', array()); + $this->setExpectedException('Alchemy\Phrasea\Exception\RuntimeException'); + $factory->getMode(true); + } + + public function testInvalidMappingDoesNotThrowsAnException() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $logger->expects($this->once()) + ->method('error') + ->with($this->isType('string')); + + $factory = new XSendFileFactory($logger, true, 'nginx', array()); + $this->assertInstanceOf('Alchemy\Phrasea\Http\XSendFile\NginxMode', $factory->getMode(false)); + } + + public function testInvalidMappingDoesNotThrowsAnExceptionByDefault() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $logger->expects($this->once()) + ->method('error') + ->with($this->isType('string')); + + $factory = new XSendFileFactory($logger, true, 'nginx', array()); + $this->assertInstanceOf('Alchemy\Phrasea\Http\XSendFile\NginxMode', $factory->getMode()); + } + + private function getNginxMapping() + { + return array(array( + 'directory' => __DIR__ . '/../../../../files/', + 'mount-point' => '/protected/' + )); + } + + private function getApacheMapping() + { + return array(array( + 'directory' => __DIR__ . '/../../../../files/', + )); + } +} diff --git a/tests/Alchemy/Tests/Phrasea/Http/XsendfileMappingTest.php b/tests/Alchemy/Tests/Phrasea/Http/XsendfileMappingTest.php deleted file mode 100644 index fabc4fcbf8..0000000000 --- a/tests/Alchemy/Tests/Phrasea/Http/XsendfileMappingTest.php +++ /dev/null @@ -1,103 +0,0 @@ - $dir, - 'mount-point' => '/protected/' - ) - )); - - $this->assertEquals('/protected='.realpath($dir), (string) $mapping); - } - - public function testMultiMapping() - { - $protected = __DIR__ . '/../../../../files/'; - $upload = __DIR__ . '/../../../../'; - $mapping = new XsendfileMapping(array( - array( - 'directory' => $protected, - 'mount-point' => '/protected/' - ), - array( - 'directory' => $upload, - 'mount-point' => '/uploads/' - ), - )); - - $this->assertEquals('/protected='.realpath($protected).',/uploads='.realpath($upload), (string) $mapping); - } - - public function testMultiMappingWithANotExsistingDir() - { - $protected = __DIR__ . '/../../../../files/'; - $mapping = new XsendfileMapping(array( - array( - 'directory' => $protected, - 'mount-point' => '/protected/' - ), - array( - 'directory' => '/path/to/nonexistent/directory', - 'mount-point' => '/test/' - ), - )); - - $this->assertEquals('/protected='.realpath($protected), (string) $mapping); - } - - public function testEmptyMapping() - { - $mapping = new XsendfileMapping(array()); - - $this->assertEquals('', (string) $mapping); - } - - /** - * @dataProvider provideVariousMappings - */ - public function testGetMapping($map, $expected) - { - $mapping = new XsendfileMapping($map); - - $this->assertEquals($expected, $mapping->getMapping()); - } - - public function provideVariousMappings() - { - return array( - array(array(), array()), - array(array(array('mount-point' => false, 'directory' => false)), array()), - array(array(array('mount-point' => 'mount', 'directory' => false)), array()), - array(array(array('mount-point' => false, 'directory' => __DIR__)), array()), - array(array(array('mount-point' => 'mount', 'directory' => __DIR__)), array(array('mount-point' => '/mount', 'directory' => __DIR__))), - array(array(array('mount-point' => '/mount/', 'directory' => __DIR__ . '/../..')), array(array('mount-point' => '/mount', 'directory' => realpath(__DIR__.'/../..')))), - ); - } - - /** - * @dataProvider provideInvalidMappings - * @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException - */ - public function testInvalidMapping($map) - { - new XsendfileMapping($map); - } - - public function provideInvalidMappings() - { - return array( - array(array('mount-point' => '/mount', 'directory' => __DIR__)), - array(array(array('mount-point' => '/mount'))), - array(array(array('directory' => __DIR__))), - ); - } -}