Skip to content

Commit e5b8041

Browse files
committed
* Feature: allow configuring custom paths to serve assets from.
* Fix: Avoid serving files outside these directories (when a relative path is given in url)
1 parent d4b77bd commit e5b8041

File tree

6 files changed

+76
-31
lines changed

6 files changed

+76
-31
lines changed

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,27 @@ return function (array $context) {
4141

4242
### Assets
4343

44-
To allow serving public bundle assets (like API-Platform's Swagger-UI) through ReactPHP, add this file to your project `config/routes/react_bundle.yaml`
44+
To allow serving assets from the public directory through ReactPHP, add this file to your project `config/routes/react_bundle.yaml`
4545
```yaml
46-
react_bundle:
47-
type: react_bundle
46+
reactphp_bundle:
47+
type: reactphp_bundle
4848
resource: .
4949
```
5050
51+
By default, only files in the `public/bundles` directory are served (like Swagger-UI in API-Platform).
52+
Additional directories and files can be registered in the bundle config at `config/bundles/zolex_react_php.yaml`:
53+
54+
```yaml
55+
zolex_react_php:
56+
asset_paths:
57+
- /bundles/
58+
- /custom-dir/
59+
- /single-file.js
60+
- /another/single/file.css
61+
```
62+
5163
### Start the server
5264

5365
```bash
5466
APP_RUNTIME="Zolex\\ReactPhpBundle\\Runtime\\ReactPhpRuntime" php public/index.php
5567
```
56-
57-
### Credits
58-
59-
This bundle is inspired by [runtime/reactphp](https://github.com/php-runtime/reactphp).

src/Action/ServeBundleAssetsAction.php

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,12 @@ public function __construct(private string $projectDir)
2222
{
2323
}
2424

25-
public function __invoke(string $file)
25+
public function __invoke(string $directory, string $file)
2626
{
27-
$path = $this->projectDir.'/public/bundles/'.$file;
28-
29-
if (!is_readable($path)) {
30-
throw new NotFoundHttpException("'$file' is not readable.");
31-
}
32-
33-
if (is_dir($path)) {
34-
throw new NotFoundHttpException("'$file' is a directory.");
27+
$baseDir = $this->projectDir.'/public'. ('/' !== $directory ? '/'.$directory : '');
28+
$path = realpath($baseDir.'/'.$file);
29+
if (false === $path || !is_readable($path) || is_dir($path) || !str_starts_with($path, $baseDir)) {
30+
throw new NotFoundHttpException();
3531
}
3632

3733
$contentType = match (pathinfo($path)['extension']) {

src/DependencyInjection/Configuration.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@ class Configuration implements ConfigurationInterface
2020
{
2121
public function getConfigTreeBuilder(): TreeBuilder
2222
{
23-
$treeBuilder = new TreeBuilder('zolex_reactphp');
24-
/*
23+
$treeBuilder = new TreeBuilder('zolex_react_php');
2524
$rootNode = $treeBuilder->getRootNode();
2625
$rootNode
2726
->children()
2827
->arrayNode('asset_paths')
29-
->arrayPrototype()
28+
->defaultValue(['/bundles'])
29+
->stringPrototype()
3030
->end()
3131
->end();
32-
*/
3332

3433
return $treeBuilder;
3534
}

src/DependencyInjection/ZolexReactPhpExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ public function load(array $configs, ContainerBuilder $container)
3434

3535
public function process(ContainerBuilder $container)
3636
{
37+
$routeLoader = $container->getDefinition('zolex.reactphp_bundle.routing.loader');
38+
$routeLoader->addMethodCall('setAssetPaths', [$this->config['asset_paths']]);
3739
}
3840
}

src/Resources/config/services.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
<service id="zolex.reactphp_bundle.routing.loader" class="Zolex\ReactPhpBundle\Routing\Loader\ReactPhpBundleLoader">
1313
<tag name="routing.loader"/>
14+
<call method="setProjectDir">
15+
<argument>%kernel.project_dir%</argument>
16+
</call>
1417
</service>
1518
</services>
1619
</container>

src/Routing/Loader/ReactPhpBundleLoader.php

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,62 @@
1919

2020
final class ReactPhpBundleLoader extends Loader
2121
{
22+
private array $assetPaths;
23+
private string $publicPath;
24+
2225
public function supports($resource, $type = null): bool
2326
{
2427
return 'reactphp_bundle' === $type;
2528
}
2629

30+
public function setProjectDir(string $path): void
31+
{
32+
$this->publicPath = rtrim($path, '/') . '/public';
33+
}
34+
35+
public function setAssetPaths(array $paths): void
36+
{
37+
$this->assetPaths = $paths;
38+
}
39+
2740
public function load(mixed $resource, ?string $type = null): RouteCollection
2841
{
2942
$routes = new RouteCollection();
30-
$routes->add(
31-
name: 'zolex:reactphp_bundle',
32-
route: new Route(
33-
path: '/bundles/{file}',
34-
defaults: [
35-
'_controller' => 'zolex.reactphp_bundle.serve_bundle_assets_action',
36-
],
37-
requirements: ['file' => '.*'],
38-
methods: ['GET']
39-
),
40-
);
43+
foreach ($this->assetPaths as $assetPath) {
44+
$target = trim($assetPath, '/');
45+
if (!is_readable($this->publicPath . '/'. $target)) {
46+
user_error('Tried to register "'. $target . '" asset path, but it does not exist or is not readable.', E_USER_WARNING);
47+
continue;
48+
}
49+
50+
if (is_file($this->publicPath . '/'. $target)) {
51+
$routes->add(
52+
name: 'zolex:reactphp_bundle:' . str_replace('/', '_', $target),
53+
route: new Route(
54+
path: '/' . $target,
55+
defaults: [
56+
'_controller' => 'zolex.reactphp_bundle.serve_bundle_assets_action',
57+
'directory' => '/',
58+
'file' => $target,
59+
],
60+
methods: ['GET']
61+
),
62+
);
63+
} else {
64+
$routes->add(
65+
name: 'zolex:reactphp_bundle:' . str_replace('/', '_', $target),
66+
route: new Route(
67+
path: '/' . $target . '/{file}',
68+
defaults: [
69+
'_controller' => 'zolex.reactphp_bundle.serve_bundle_assets_action',
70+
'directory' => $target,
71+
],
72+
requirements: ['file' => '.*'],
73+
methods: ['GET']
74+
),
75+
);
76+
}
77+
}
4178

4279
return $routes;
4380
}

0 commit comments

Comments
 (0)