Added the ability to specify files to be directly linked via the 'direct_links' configuration option

This commit is contained in:
Chris Kankiewicz
2025-06-19 20:25:02 -07:00
parent 37e81da464
commit 4f875184a7
8 changed files with 70 additions and 11 deletions

View File

@@ -62,6 +62,15 @@ return [
*/
'readmes_first' => env('READMES_FIRST', false),
/**
* Comma separated list of file patterns to be directly linked. Directly
* linked files will not be served by Directory Lister but handled by the
* web server directly. This setting has no effect when FILES_PATH is set.
*
* Default value: null
*/
'direct_links' => env('DIRECT_LINKS', null),
/**
* Enable downloading of directories as a zip archive.
*

View File

@@ -4,23 +4,51 @@ declare(strict_types=1);
namespace App\ViewFunctions;
use PHLAK\Splat\Glob;
use PHLAK\Splat\Pattern;
class FileUrl extends Url
{
protected string $name = 'file_url';
/** Return the URL for a given path and action. */
/** Direct links pattern cache. */
private ?Pattern $pattern = null;
public function __invoke(string $path = '/'): string
{
if (is_file($path)) {
return sprintf('?file=%s', $this->escape($this->normalizePath($path)));
}
$normalizedPath = $this->normalizePath($path);
$path = $this->normalizePath($path);
if ($path === '') {
if ($normalizedPath === '') {
return '';
}
return sprintf('?dir=%s', $this->escape($path));
$fullPath = $this->container->call('full_path', ['path' => $normalizedPath]);
$escapedPath = $this->escape($normalizedPath);
if (is_file($fullPath)) {
return $this->isDirectLink($fullPath) ? $escapedPath : sprintf('?file=%s', $escapedPath);
}
return sprintf('?dir=%s', $escapedPath);
}
/** Determine if a file should be directly linked. */
private function isDirectLink(string $path): bool
{
if ($this->config->get('base_path') !== $this->config->get('files_path')) {
return false;
}
if ($this->config->get('direct_links') === null) {
return false;
}
if (! $this->pattern instanceof Pattern) {
$this->pattern = Pattern::make(sprintf('%s{%s}', Pattern::escape(
$this->config->get('files_path') . DIRECTORY_SEPARATOR
), $this->config->get('direct_links')));
}
return Glob::matchStart($this->pattern, $path);
}
}

View File

@@ -6,6 +6,7 @@ namespace App\ViewFunctions;
use App\Config;
use App\Support\Str;
use DI\Container;
class Url extends ViewFunction
{
@@ -13,7 +14,8 @@ class Url extends ViewFunction
/** @param non-empty-string $directorySeparator */
public function __construct(
private Config $config,
protected Container $container,
protected Config $config,
private string $directorySeparator = DIRECTORY_SEPARATOR
) {}

View File

@@ -8,7 +8,6 @@ class ZipUrl extends Url
{
protected string $name = 'zip_url';
/** Return the URL for a given path and action. */
public function __invoke(string $path = '/'): string
{
$path = $this->normalizePath($path);

View File

@@ -15,10 +15,15 @@ class FileUrlTest extends TestCase
#[Test]
public function it_can_return_a_url(): void
{
$this->container->set('direct_links', '**/index.{htm,html},**/*.php');
$url = $this->container->get(FileUrl::class);
// Root
$this->assertEquals('', $url('/'));
$this->assertEquals('', $url('./'));
// Subdirectories
$this->assertEquals('?dir=some/path', $url('some/path'));
$this->assertEquals('?dir=some/path', $url('./some/path'));
$this->assertEquals('?dir=some/path', $url('./some/path'));
@@ -27,6 +32,16 @@ class FileUrlTest extends TestCase
$this->assertEquals('?dir=0/path', $url('0/path'));
$this->assertEquals('?dir=1/path', $url('1/path'));
$this->assertEquals('?dir=0', $url('0'));
// Files
$this->assertEquals('?file=subdir/alpha.scss', $url('subdir/alpha.scss'));
$this->assertEquals('?file=subdir/bravo.js', $url('subdir/bravo.js'));
$this->assertEquals('?file=subdir/charlie.bash', $url('subdir/charlie.bash'));
// Direct Links
$this->assertEquals('direct_links/index.htm', $url('direct_links/index.htm'));
$this->assertEquals('direct_links/index.html', $url('direct_links/index.html'));
$this->assertEquals('direct_links/test.php', $url('direct_links/test.php'));
}
#[Test]
@@ -44,7 +59,8 @@ class FileUrlTest extends TestCase
$this->assertEquals('?dir=1\path', $url('1\path'));
}
public function test_url_segments_are_url_encoded(): void
#[Test]
public function url_segments_are_url_encoded(): void
{
$url = $this->container->get(FileUrl::class);

View File

@@ -0,0 +1 @@
<h1>Test file; please ignore</h1>

View File

@@ -0,0 +1 @@
<h1>Test file; please ignore</h1>

View File

@@ -0,0 +1,3 @@
<?php
echo 'Test file; please ignore'