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

[Loader] Add bundle resources to safe path when requested #883

Merged
merged 3 commits into from
Feb 28, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Binary/Locator/FileSystemInsecureLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ class FileSystemInsecureLocator extends FileSystemLocator
*/
protected function generateAbsolutePath($root, $path)
{
if (false !== strpos($path, '..'.DIRECTORY_SEPARATOR)) {
if (false !== strpos($path, '..'.DIRECTORY_SEPARATOR) ||
false !== strpos($path, DIRECTORY_SEPARATOR.'..') ||
false === file_exists($absolute = $root.DIRECTORY_SEPARATOR.$path)) {
return false;
}

return file_exists($absolute = $root.DIRECTORY_SEPARATOR.$path) ? $absolute : false;
return $absolute;
}
}
43 changes: 40 additions & 3 deletions Binary/Locator/FileSystemLocator.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,52 @@ public function setOptions(array $options = array())
* @return string
*/
public function locate($path)
{
if (false !== $absolute = $this->locateUsingRootPlaceholder($path)) {
return $this->sanitizeAbsolutePath($absolute);
}

if (false !== $absolute = $this->locateUsingRootPathsSearch($path)) {
return $this->sanitizeAbsolutePath($absolute);
}

throw new NotLoadableException(sprintf('Source image not resolvable "%s" in root path(s) "%s"',
$path, implode(':', $this->roots)));
}

/**
* @param string $path
*
* @return bool|string
*/
private function locateUsingRootPathsSearch($path)
{
foreach ($this->roots as $root) {
if (false !== $absolute = $this->generateAbsolutePath($root, $path)) {
return $this->sanitizeAbsolutePath($absolute);
return $absolute;
}
}

throw new NotLoadableException(sprintf('Source image not resolvable "%s" in root path(s) "%s"',
$path, implode(':', $this->roots)));
return false;
}

/**
* @param string $path
*
* @return bool|string
*/
private function locateUsingRootPlaceholder($path)
{
if (0 !== strpos($path, '@') || 1 !== preg_match('{@(?<name>[^:]+):(?<path>.+)}', $path, $matches)) {
return false;
}

if (isset($this->roots[$matches['name']])) {
return $this->generateAbsolutePath($this->roots[$matches['name']], $matches['path']);
}

throw new NotLoadableException(sprintf('Invalid root placeholder "%s" for path "%s"',
$matches['name'], $matches['path']));
}

/**
Expand Down
101 changes: 100 additions & 1 deletion DependencyInjection/Factory/Loader/FileSystemLoaderFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Liip\ImagineBundle\DependencyInjection\Factory\Loader;

use Liip\ImagineBundle\Exception\InvalidArgumentException;
use Liip\ImagineBundle\Utility\Framework\SymfonyFramework;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand All @@ -25,7 +26,7 @@ class FileSystemLoaderFactory extends AbstractLoaderFactory
public function create(ContainerBuilder $container, $loaderName, array $config)
{
$definition = $this->getChildLoaderDefinition();
$definition->replaceArgument(2, $config['data_root']);
$definition->replaceArgument(2, $this->resolveDataRoots($config['data_root'], $config['bundle_resources'], $container));
$definition->replaceArgument(3, $this->createLocatorReference($config['locator']));

return $this->setTaggedLoaderDefinition($loaderName, $definition, $container);
Expand Down Expand Up @@ -56,14 +57,112 @@ public function addConfiguration(ArrayNodeDefinition $builder)
->ifString()
->then(function ($value) { return array($value); })
->end()
->treatNullLike(array())
->treatFalseLike(array())
->defaultValue(array('%kernel.root_dir%/../web'))
->prototype('scalar')
->cannotBeEmpty()
->end()
->end()
->arrayNode('bundle_resources')
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')
->defaultFalse()
->end()
->enumNode('access_control_type')
->values(array('blacklist', 'whitelist'))
->info('Sets the access control method applied to bundle names in "access_control_list" into a blacklist or whitelist.')
->defaultValue('blacklist')
->end()
->arrayNode('access_control_list')
->defaultValue(array())
->prototype('scalar')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end();
}


/*
* @param string[] $staticPaths
* @param array $config
* @param ContainerBuilder $container
*
* @return string[]
*/
private function resolveDataRoots(array $staticPaths, array $config, ContainerBuilder $container)
{
if (false === $config['enabled']) {
return $staticPaths;
}

$resourcePaths = array();

foreach ($this->getBundleResourcePaths($container) as $name => $path) {
if (('whitelist' === $config['access_control_type']) === in_array($name, $config['access_control_list']) && is_dir($path)) {
$resourcePaths[$name] = $path;
}
}

return array_merge($staticPaths, $resourcePaths);
}

/**
* @param ContainerBuilder $container
*
* @return string[]
*/
private function getBundleResourcePaths(ContainerBuilder $container)
{
if ($container->hasParameter('kernel.bundles_metadata')) {
$paths = $this->getBundlePathsUsingMetadata($container->getParameter('kernel.bundles_metadata'));
} else {
$paths = $this->getBundlePathsUsingNamedObj($container->getParameter('kernel.bundles'));
}

return array_map(function ($path) {
return $path.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'public';
}, $paths);
}

/**
* @param array[] $metadata
*
* @return string[]
*/
private function getBundlePathsUsingMetadata(array $metadata)
{
return array_combine(array_keys($metadata), array_map(function ($data) {
return $data['path'];
}, $metadata));
}

/**
* @param string[] $classes
*
* @return string[]
*/
private function getBundlePathsUsingNamedObj(array $classes)
{
$paths = array();

foreach ($classes as $c) {
try {
$r = new \ReflectionClass($c);
} catch (\ReflectionException $exception) {
throw new InvalidArgumentException(sprintf('Unable to resolve bundle "%s" while auto-registering bundle resource paths.', $c), null, $exception);
}

$paths[$r->getShortName()] = dirname($r->getFileName());
}

return $paths;
}

/**
* @param string $reference
*
Expand Down
75 changes: 69 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,12 @@ $response = $imagine
```


## Images Outside Data Root
## Data Roots

When your setup requires your source images reside outside the web root,
you have to set your loader's `data_root` parameter in your configuration
(often `app/config/config.yml`) with the absolute path to your source image
location.
By default, Symfony's `web/` directory is registered as a data root to load
assets from. For many installations this will be sufficient, but sometime you
may need to load images from other locations. To do this, you must set the
`data_root` parameter in your configuration (often located at `app/config/config.yml`).

```yml
liip_imagine:
Expand All @@ -382,7 +382,70 @@ liip_imagine:
data_root: /path/to/source/images/dir
```

This location must be readable by your web server. On a system that supports
As of version `1.7.2` you can register multiple data root paths, and the
file locator will search each for the requested file.

```yml
liip_imagine:
loaders:
default:
filesystem:
data_root:
- /path/foo
- /path/bar
```

As of version `1.7.3` you ask for the public resource paths from all registered bundles
to be auto-registered as data roots. This allows you to load assets from the
`Resources/public` folders that reside within the loaded bundles. To enable this
feature, set the `bundle_resources.enabled` configuration option to `true`.

```yml
liip_imagine:
loaders:
default:
filesystem:
bundle_resources:
enabled: true
```

If you want to register some of the `Resource/public` folders, but not all, you can do
so by blacklisting the bundles you don't want registered or whitelisting the bundles you
do want registered. For example, to blacklist (not register) the bundles "FooBundle" and
"BarBundle", you would use the following configuration.

```yml
liip_imagine:
loaders:
default:
filesystem:
bundle_resources:
enabled: true
access_control_type: blacklist
access_control_list:
- FooBundle
- BarBundle
```

Alternatively, if you want to whitelist (only register) the bundles "FooBundle" and "BarBundle",
you would use the following configuration.

```yml
liip_imagine:
loaders:
default:
filesystem:
bundle_resources:
enabled: true
access_control_type: whitelist
access_control_list:
- FooBundle
- BarBundle
```

### Permissions

Image locations must be readable by your web server. On a system that supports
`setfacl` (such as Linux/BSD), use

```sh
Expand Down
Loading