Skip to content

Commit

Permalink
Refator for working like Laravel 11
Browse files Browse the repository at this point in the history
  • Loading branch information
oanhnn committed Jan 2, 2025
1 parent 24c31fa commit 1c5c54f
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 25 deletions.
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# Laravel Local Temporary Url

[![Latest Version on Packagist](https://img.shields.io/packagist/v/rabiloo/laravel-local-temporary-url.svg)](https://packagist.org/packages/rabiloo/laravel-local-temporary-url)
[![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/rabiloo/laravel-local-temporary-url/Tests?label=tests)](https://github.com/rabiloo/laravel-local-temporary-url/actions?query=workflow%3ATests+branch%3Amaster)
[![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/rabiloo/laravel-local-temporary-url/Check%20&%20fix%20styling?label=code%20style)](https://github.com/rabiloo/laravel-local-temporary-url/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amaster)
[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/rabiloo/laravel-local-temporary-url/run-tests.yml?branch=master&label=tests)](https://github.com/rabiloo/laravel-local-temporary-url/actions?query=workflow%3ATests+branch%3Amaster)
[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/rabiloo/laravel-local-temporary-url/check-styling.yml?branch=master&label=code%20style)](https://github.com/rabiloo/laravel-local-temporary-url/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amaster)
[![Total Downloads](https://img.shields.io/packagist/dt/rabiloo/laravel-local-temporary-url.svg)](https://packagist.org/packages/rabiloo/laravel-local-temporary-url)

Provides `temporaryUrl()` method for local filesystem driver

![Laravel Local Temporary Url](https://banners.beyondco.de/Local%20Temporary%20Url.png?theme=light&packageManager=composer+require&packageName=rabiloo%2Flaravel-local-temporary-url&pattern=brickWall&style=style_1&description=Provides+%60temporaryUrl%60+method+for+local+filesystem+driver&md=1&showWatermark=0&fontSize=100px&images=clock)

> **NOTE:** Laravel 11 comes with native support for this feature. See https://laravel.com/docs/11.x/filesystem#temporary-urls
> This package provides the same feature for Laravel 9+ versions.
## Installation

You can install the package via composer:
Expand All @@ -19,6 +22,45 @@ composer require rabiloo/laravel-local-temporary-url

## Usage

Enable by config (See https://laravel.com/docs/11.x/filesystem#enabling-local-temporary-urls )

```php
# config/filesystems.php

return [
'disks' => [
//
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
'serve' => true, // Enable file server, default URL is `/local/temp/{path}?expires=xxx&signature=xxx`
],

'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public', // Enable file server with `visibility` = `public`
'throw' => false,
],

// Custom local disk
'local2' => [
'driver' => 'local',
'root' => storage_path('app/local2'),
'throw' => false,
'serve' => true, // Enable file server
'url' => 'local2/tmp', // The URL will be `/local2/tmp/{path}?expires=xxx&signature=xxx`
],

//...
],
];
```

Make temporary URL

```php
use Illuminate\Support\Facades\Storage;

Expand Down
63 changes: 63 additions & 0 deletions src/ServeFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Rabiloo\Laravel\LocalTemporaryUrl;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use League\Flysystem\PathTraversalDetected;
use Symfony\Component\HttpFoundation\StreamedResponse;

class ServeFile
{
/**
* Create a new invokable controller to serve files.
*/
public function __construct(
protected string $disk,
protected array $config,
protected bool $isProduction,
) {
//
}

/**
* Handle the incoming request.
*/
public function __invoke(Request $request, string $path): StreamedResponse
{
abort_unless(
$this->hasValidSignature($request),
$this->isProduction ? 404 : 403
);
try {
abort_unless(Storage::disk($this->disk)->exists($path), 404);

/** @var \Illuminate\Filesystem\FilesystemAdapter $disk */
$disk = Storage::disk($this->disk);
$headers = [
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
'Content-Security-Policy' => "default-src 'none'; style-src 'unsafe-inline'; sandbox",
];

return tap(
$disk->response($path, null, $headers),
function ($response) use ($headers) {
if (! $response->headers->has('Content-Security-Policy')) {
$response->headers->replace();
}
}
);
} catch (PathTraversalDetected $e) {
abort(404);
}
}

/**
* Determine if the request has a valid signature if applicable.
*/
protected function hasValidSignature(Request $request): bool
{
return ($this->config['visibility'] ?? 'private') === 'public' || URL::hasValidRelativeSignature($request);
}
}
65 changes: 44 additions & 21 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

use DateTimeInterface;
use Illuminate\Contracts\Foundation\CachesRoutes;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use Symfony\Component\HttpFoundation\StreamedResponse;

class ServiceProvider extends BaseServiceProvider
{
Expand All @@ -19,28 +19,51 @@ class ServiceProvider extends BaseServiceProvider
*/
public function boot()
{
if (! ($this->app instanceof CachesRoutes && $this->app->routesAreCached())) {
Route::get('local/temp/{path}', function (string $path): StreamedResponse {
/** @var \Illuminate\Filesystem\FilesystemAdapter $disk */
$disk = Storage::disk('local');

return $disk->download($path);
})
->where('path', '.*')
->name('local.temp')
->middleware(['web', 'signed']);
if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) {
return;
}

/** @var \Illuminate\Filesystem\FilesystemAdapter $disk */
$disk = Storage::disk('local');
$disk->buildTemporaryUrlsUsing(
function (string $path, DateTimeInterface $expiration, array $options = []) {
return URL::temporarySignedRoute(
'local.temp',
$expiration,
array_merge($options, ['path' => $path])
);
foreach ($this->app['config']['filesystems.disks'] ?? [] as $disk => $config) {
if (! $this->shouldServeFiles($config)) {
continue;
}
);

$this->app->booted(function ($app) use ($disk, $config) {
$uri = isset($config['url'])
? rtrim(parse_url($config['url'])['path'], '/')
: $disk . '/temp';

$isProduction = $app->isProduction();

Route::get(
$uri . '/{path}',
fn (Request $request, string $path) => (new ServeFile(
$disk,
$config,
$isProduction
))($request, $path)
)->where('path', '.*')->name($disk . '.temp');

Storage::disk($disk)->buildTemporaryUrlsUsing(
fn (string $path, DateTimeInterface $expiration, array $options = []) => URL::temporarySignedRoute(
$disk . '.temp',
$expiration,
array_merge($options, ['path' => $path]),
false,
)
);
});
}
}

/**
* Determine if the disk is serveable.
*
* @param array $config
* @return bool
*/
protected function shouldServeFiles(array $config)
{
return $config['driver'] === 'local' && ($config['serve'] ?? false);
}
}
24 changes: 22 additions & 2 deletions tests/ServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,30 @@

namespace Test;

use Illuminate\Contracts\Config\Repository;
use Illuminate\Support\Facades\Storage;
use Orchestra\Testbench\TestCase;
use Rabiloo\Laravel\LocalTemporaryUrl\ServiceProvider;

class ServiceProviderTest extends TestCase
{
/**
* Define environment setup.
*
* @api
*
* @param \Illuminate\Foundation\Application $app
* @return void
*/
protected function defineEnvironment($app)
{
// Setup local filesystem config
tap($app['config'], function (Repository $config) {
// enable serve file
$config->set('filesystems.disks.local.serve', true);
});
}

/**
* Get package providers.
*
Expand All @@ -26,14 +44,16 @@ protected function getPackageProviders($app)
public function it_should_has_temporary_url()
{
$path = 'test/file.txt';
Storage::disk('local')->put($path, 'Hello world');
$content = 'Hello world';
Storage::disk('local')->put($path, $content);

$url = Storage::disk('local')->temporaryUrl($path, now()->addSeconds(1));
$this->assertIsString($url);

$response = $this->get($url);
$response->assertOk()
->assertDownload('file.txt');
->assertHeader('Content-Disposition', 'inline; filename=file.txt')
->assertStreamedContent($content);

// Sleep 2 seconds
sleep(2);
Expand Down

0 comments on commit 1c5c54f

Please sign in to comment.