Skip to content
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: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ jobs:
-v "${{ github.workspace }}":/var/www/html \
-w /var/www/html \
laravelsail/php84-composer:latest \
sh -lc "composer install --no-interaction --prefer-dist && vendor/bin/phpunit"
sh -lc "
composer install --no-interaction --prefer-dist &&
vendor/bin/phpunit &&
vendor/bin/pint --test
"
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"ext-openssl": "*"
},
"require-dev": {
"orchestra/testbench": "^10.4"
"orchestra/testbench": "^10.4",
"laravel/pint": "^1.22"
},
"autoload-dev": {
"psr-4": {
Expand All @@ -49,4 +50,4 @@
"@php vendor/bin/testbench serve --ansi"
]
}
}
}
68 changes: 67 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions config/image-proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
return [
'route_prefix' => env('IMAGE_PROXY_ROUTE_PREFIX', 'img'),
'route_names' => [
'image.proxy.short' => env('IMAGE_PROXY_ROUTE_SHORT', 'image.proxy.short'),
'image.proxy.short' => env('IMAGE_PROXY_ROUTE_SHORT', 'image.proxy.short'),
'image.proxy.filename' => env('IMAGE_PROXY_ROUTE_FILENAME', 'image.proxy.filename'),
],
'route_middleware' => [],
'disks' => [
'source' => env('IMAGE_PROXY_DISK_SOURCE', 'local'),
'cache' => env('IMAGE_PROXY_DISK_CACHE', 'local'),
'cache' => env('IMAGE_PROXY_DISK_CACHE', 'local'),
],
'encryptor' => Bst27\ImageProxy\Services\OpenSslPayloadEncryptor::class,
'token_encoder' => Bst27\ImageProxy\Services\Base64UrlTokenEncoder::class,
'manipulation_strategy' => [
'default' => [
'class' => \Bst27\ImageProxy\Services\ImageManipulator\DefaultManipulator::class,
'class' => \Bst27\ImageProxy\Services\ImageManipulator\DefaultManipulator::class,
'params' => [],
],
],
Expand Down
6 changes: 3 additions & 3 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
use Illuminate\Support\Facades\Route;

$prefix = config('image-proxy.route_prefix');
$names = config('image-proxy.route_names');
$names = config('image-proxy.route_names');
$middleware = config('image-proxy.route_middleware');

Route::group([
'prefix' => $prefix,
'prefix' => $prefix,
'middleware' => $middleware,
], function() use ($names) {
], function () use ($names) {
Route::get('{token}.{ext}', [ImageProxyController::class, 'serveShort'])
->where('token', '[A-Za-z0-9\-_]+')
->where('ext', '[A-Za-z0-9]+')
Expand Down
4 changes: 0 additions & 4 deletions src/Contracts/ImageManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ interface ImageManipulator
/**
* This function has to return the content of the manipulated file as string.
* It is given the original file content and any additional parameters if available.
*
* @param string $fileContent
* @param array $params
* @return string
*/
public function manipulate(string $fileContent, array $params): string;
}
1 change: 1 addition & 0 deletions src/Contracts/PayloadEncryptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
interface PayloadEncryptor
{
public function encrypt(string $payload): string;

public function decrypt(string $cipher): string;
}
1 change: 1 addition & 0 deletions src/Contracts/TokenEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
interface TokenEncoder
{
public function encode(string $cipher): string;

public function decode(string $token): string;
}
35 changes: 18 additions & 17 deletions src/Http/Controllers/ImageProxyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public function serveFilename(string $token, string $filename): Response

private function decryptPayload(string $token): array
{
$encoder = app(TokenEncoder::class);
$cipher = $encoder->decode($token);
$encoder = app(TokenEncoder::class);
$cipher = $encoder->decode($token);
$encryptor = app(PayloadEncryptor::class);
$json = $encryptor->decrypt($cipher);
$json = $encryptor->decrypt($cipher);

$data = json_decode($json, true);
if (! $data || ! isset($data['path'], $data['v'])) {
Expand All @@ -54,11 +54,11 @@ private function decryptPayload(string $token): array
private function processAndDeliver(array $data): Response
{
$sourceDisk = Storage::disk(config('image-proxy.disks.source'));
$cacheDisk = Storage::disk(config('image-proxy.disks.cache'));
$cacheDisk = Storage::disk(config('image-proxy.disks.cache'));

$sourcePath = $data['path'];

if (!$sourceDisk->exists($sourcePath)) {
if (! $sourceDisk->exists($sourcePath)) {
abort(404);
}

Expand All @@ -69,23 +69,23 @@ private function processAndDeliver(array $data): Response
}

$strategyKey = $data['strategy'];
$strategies = config('image-proxy.manipulation_strategy');
$strategies = config('image-proxy.manipulation_strategy');

if (! isset($strategies[$strategyKey])) {
abort(500, "Unknown image-proxy strategy: {$strategyKey}");
}

$conf = $strategies[$strategyKey];
$class = $conf['class'];
$default = $conf['params'] ?? [];
$mergeParams = $data['mergeParams'] ?? [];
$params = array_merge($default, $mergeParams);
$conf = $strategies[$strategyKey];
$class = $conf['class'];
$default = $conf['params'] ?? [];
$mergeParams = $data['mergeParams'] ?? [];
$params = array_merge($default, $mergeParams);

$ext = pathinfo($sourcePath, PATHINFO_EXTENSION);
$cacheHash = $fileHash . '-' . md5(json_encode($params));
$cacheKey = $cacheHash . '.' . $ext;
$ext = pathinfo($sourcePath, PATHINFO_EXTENSION);
$cacheHash = $fileHash.'-'.md5(json_encode($params));
$cacheKey = $cacheHash.'.'.$ext;

if (!$cacheDisk->exists($cacheKey)) {
if (! $cacheDisk->exists($cacheKey)) {
/** @var ImageManipulator $manipulator */
$manipulator = app($class);
$fileContent = $manipulator->manipulate($fileContent, $params);
Expand All @@ -94,10 +94,11 @@ private function processAndDeliver(array $data): Response
}

$stream = $cacheDisk->readStream($cacheKey);
return new StreamedResponse(function() use ($stream) {

return new StreamedResponse(function () use ($stream) {
fpassthru($stream);
}, 200, [
'Content-Type' => $cacheDisk->mimeType($cacheKey),
'Content-Type' => $cacheDisk->mimeType($cacheKey),
'Cache-Control' => 'public, max-age=31536000, immutable',
]);
}
Expand Down
1 change: 0 additions & 1 deletion src/ImageProxyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Bst27\ImageProxy\Contracts\PayloadEncryptor;
use Bst27\ImageProxy\Contracts\TokenEncoder;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;

class ImageProxyServiceProvider extends ServiceProvider
Expand Down
2 changes: 2 additions & 0 deletions src/Services/Base64UrlTokenEncoder.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

namespace Bst27\ImageProxy\Services;

use Bst27\ImageProxy\Contracts\TokenEncoder;
Expand All @@ -13,6 +14,7 @@ public function encode(string $cipher): string
public function decode(string $token): string
{
$b64 = strtr($token, '-_', '+/');

return (string) base64_decode($b64);
}
}
2 changes: 1 addition & 1 deletion src/Services/ImageManipulator/DefaultManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class DefaultManipulator implements ImageManipulator
{
public function manipulate(string $fileContent, array $params): string
{
$tmpFile = tempnam(sys_get_temp_dir(), 'img');
$tmpFile = tempnam(sys_get_temp_dir(), 'img');
file_put_contents($tmpFile, $fileContent);

try {
Expand Down
2 changes: 2 additions & 0 deletions src/Services/OpenSslPayloadEncryptor.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<?php

namespace Bst27\ImageProxy\Services;

use Bst27\ImageProxy\Contracts\PayloadEncryptor;

class OpenSslPayloadEncryptor implements PayloadEncryptor
{
private const HASH_ALGORITHM = 'sha256';

private const CIPHER_ALGORITHM = 'AES-256-ECB';

public function encrypt(string $payload): string
Expand Down
20 changes: 9 additions & 11 deletions src/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use Bst27\ImageProxy\Contracts\PayloadEncryptor;
use Bst27\ImageProxy\Contracts\TokenEncoder;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;

Expand All @@ -13,8 +12,7 @@ function proxy_image(
?string $strategyKey = 'default',
false|string $fileName = false,
array $mergeParams = []
): string
{
): string {
$sourceDisk = Storage::disk(config('image-proxy.disks.source'));

if (! $sourceDisk->exists($path)) {
Expand All @@ -25,20 +23,20 @@ function proxy_image(
$fileHash = md5($fileContent);

$payload = [
'path' => $path,
'path' => $path,
'strategy' => $strategyKey,
'mergeParams' => $mergeParams,
'v' => $fileHash,
'mergeParams' => $mergeParams,
'v' => $fileHash,
'filename' => $fileName,
];

$json = json_encode($payload, JSON_UNESCAPED_SLASHES);
$json = json_encode($payload, JSON_UNESCAPED_SLASHES);
$encryptor = app(PayloadEncryptor::class);
$cipher = $encryptor->encrypt($json);
$encoder = app(TokenEncoder::class);
$token = $encoder->encode($cipher);
$cipher = $encryptor->encrypt($json);
$encoder = app(TokenEncoder::class);
$token = $encoder->encode($cipher);

$ext = pathinfo($path, PATHINFO_EXTENSION);
$ext = pathinfo($path, PATHINFO_EXTENSION);
$routeName = $fileName === false
? 'image.proxy.short'
: 'image.proxy.filename';
Expand Down
6 changes: 3 additions & 3 deletions tests/Feature/ImageProxyControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class ImageProxyControllerTest extends TestCase
{
public function testServeFilenameRejectsWrongFilename()
public function test_serve_filename_rejects_wrong_filename()
{
$good = proxy_image('images/60x40.png', 'default', 'bar.png', []);
$bad = preg_replace('/bar\.png$/', 'baz.png', $good);
Expand All @@ -15,7 +15,7 @@ public function testServeFilenameRejectsWrongFilename()
$this->get($good)->assertStatus(200);
}

public function testServeShortRejectsWrongExtension()
public function test_serve_short_rejects_wrong_extension()
{
$good = proxy_image('images/60x40.png');
$bad = preg_replace('/\.png$/', '.dat', $good);
Expand All @@ -24,7 +24,7 @@ public function testServeShortRejectsWrongExtension()
$this->get($good)->assertStatus(200);
}

public function testInvalidTokenReturns404()
public function test_invalid_token_returns404()
{
$this->get('/img/invalid-token-123.png')->assertStatus(404);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/ProxyImageHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class ProxyImageHelperTest extends TestCase
{
public function testHelperAndDefaultManipulatorIntegration()
public function test_helper_and_default_manipulator_integration()
{
$url = proxy_image('images/60x40.jpg', 'default', false, []);

Expand Down
2 changes: 1 addition & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protected function getEnvironmentSetUp($app): void
$app['config']->set('image-proxy.disks.source', 'fixtures');
$app['config']->set('filesystems.disks.fixtures', [
'driver' => 'local',
'root' => __DIR__ . '/Fixtures',
'root' => __DIR__.'/Fixtures',
]);
}
}
Loading