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
3,201 changes: 1,705 additions & 1,496 deletions .phpstorm.meta.php

Large diffs are not rendered by default.

35,188 changes: 20,702 additions & 14,486 deletions _ide_helper.php

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion app/Helper.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Illuminate\Support\Str;
use enshrined\svgSanitize\Sanitizer;

/**
* @param $bytes
Expand Down Expand Up @@ -129,7 +130,11 @@ function isImage(string $file, string $extension): bool
fwrite($handle, $file);
fclose($handle);

if ($extension == 'svg') {
if ($extension === 'svg') {
$sanitizer = new Sanitizer();
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($tempFileName));
file_put_contents($tempFileName, $sanitizedSvg);

return 'image/svg+xml' === mime_content_type($tempFileName);
}

Expand Down
29 changes: 28 additions & 1 deletion app/Http/Controllers/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Illuminate\Validation\ValidationException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use enshrined\svgSanitize\Sanitizer;

class ItemController extends Controller
{
Expand Down Expand Up @@ -236,7 +237,23 @@ public static function storelogic(Request $request, $id = null): Item
]);

if ($request->hasFile('file')) {
$path = $request->file('file')->store('icons', 'public');
$image = $request->file('file');
$extension = $image->getClientOriginalExtension();

if ($extension === 'svg') {
$sanitizer = new Sanitizer();
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($image->getRealPath()));

// Verify that the sanitization removed malicious content
if (strpos($sanitizedSvg, '<script>') !== false) {
throw ValidationException::withMessages(['file' => 'SVG contains malicious content and cannot be uploaded.']);
}

// Save the sanitized SVG back to the file
file_put_contents($image->getRealPath(), $sanitizedSvg);
}

$path = $image->store('icons', 'public');
$request->merge([
'icon' => $path,
]);
Expand All @@ -257,6 +274,16 @@ public static function storelogic(Request $request, $id = null): Item

$contents = file_get_contents($request->input('icon'), false, stream_context_create($options));

if ($extension === 'svg') {
$sanitizer = new Sanitizer();
$contents = $sanitizer->sanitize($contents);

// Verify that the sanitization removed malicious content
if (strpos($contents, '<script>') !== false) {
throw ValidationException::withMessages(['file' => 'SVG contains malicious content and cannot be uploaded.']);
}
}

if (!isImage($contents, $extension)) {
throw ValidationException::withMessages(['file' => 'Icon must be an image.']);
}
Expand Down
27 changes: 21 additions & 6 deletions app/Http/Controllers/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Setting;
use App\SettingGroup;
use Exception;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
Expand Down Expand Up @@ -68,16 +69,30 @@ public function update(Request $request, int $id): RedirectResponse

if ($setting->type === 'image') {
$validatedData = $request->validate([
'value' => 'image'
'value' => 'image',
]);

if (!$request->hasFile('value')) {
throw new \Exception(
'file_too_big'
);
throw new \Exception('file_too_big');
}

$path = $request->file('value')->store('backgrounds', 'public');
$image = $request->file('value');
$extension = $image->getClientOriginalExtension();

if ($extension === 'svg') {
$sanitizer = new Sanitizer();
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($image->getRealPath()));

// Verify that the sanitization removed malicious content
if (strpos($sanitizedSvg, '<script>') !== false) {
throw new \Exception('SVG contains malicious content and cannot be uploaded.');
}

// Save the sanitized SVG back to the file
file_put_contents($image->getRealPath(), $sanitizedSvg);
}

$path = $image->store('backgrounds', 'public');

if ($path === null) {
throw new \Exception('file_not_stored');
Expand All @@ -99,7 +114,7 @@ public function update(Request $request, int $id): RedirectResponse
} catch (Exception $e) {
return redirect($route)
->with([
'errors' => collect([__('app.alert.error.'.$e->getMessage())]),
'errors' => collect([__('app.alert.error.' . $e->getMessage())]),
]);
}
}
Expand Down
19 changes: 17 additions & 2 deletions app/Setting.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Http\Request;
use Illuminate\Session\SessionManager;
use Illuminate\Session\Store;
use enshrined\svgSanitize\Sanitizer;

/**
* App\Setting
Expand Down Expand Up @@ -70,9 +71,23 @@ class Setting extends Model

public static function getInput(Request $request): object
{
$image = $request->file('value');
if ($image && $image->getClientOriginalExtension() === 'svg') {
$sanitizer = new Sanitizer();
$sanitizedSvg = $sanitizer->sanitize(file_get_contents($image->getRealPath()));

// Verify that the sanitization removed malicious content
if (strpos($sanitizedSvg, '<script>') !== false) {
throw new \Exception('SVG contains malicious content and cannot be uploaded.');
}

// Save the sanitized SVG back to the file
file_put_contents($image->getRealPath(), $sanitizedSvg);
}

return (object) [
'value' => $request->input('value'),
'image' => $request->file('value'),
'image' => $image,
];
}

Expand Down Expand Up @@ -192,7 +207,7 @@ public function getEditValueAttribute()

return $value;
}

public function group(): BelongsTo
{
return $this->belongsTo(\App\SettingGroup::class, 'group_id');
Expand Down
11 changes: 6 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@
"type": "project",
"require": {
"php": "^8.2",
"ext-intl": "*",
"ext-json": "*",
"enshrined/svg-sanitize": "^0.21.0",
"graham-campbell/github": "^12.5",
"guzzlehttp/guzzle": "^7.8",
"laravel/framework": "^11.45",
"laravel/tinker": "^2.9",
"laravel/ui": "^4.4",
"nunomaduro/collision": "^8.0",
"symfony/yaml": "^7.0",
"ext-json": "*",
"ext-intl": "*",
"league/flysystem-aws-s3-v3": "^3.0",
"nunomaduro/collision": "^8.0",
"spatie/laravel-html": "^3.11",
"spatie/laravel-ignition": "^2.4",
"spatie/laravel-html": "^3.11"
"symfony/yaml": "^7.0"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.0",
Expand Down
51 changes: 48 additions & 3 deletions composer.lock

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

24 changes: 24 additions & 0 deletions tests/Feature/SVGSanitizerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
use Tests\TestCase;
use enshrined\svgSanitize\Sanitizer;

class SVGSanitizerTest extends TestCase
{
public function testSvgSanitization()
{
$sanitizer = new Sanitizer();
$maliciousSvg = '<svg><script>alert("XSS")</script></svg>';
$sanitizedSvg = $sanitizer->sanitize($maliciousSvg);

$this->assertStringNotContainsString('<script>', $sanitizedSvg);
}

public function testValidSvgSanitization()
{
$sanitizer = new Sanitizer();
$validSvg = '<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" /></svg>';
$sanitizedSvg = $sanitizer->sanitize($validSvg);

$this->assertStringContainsString('<circle', $sanitizedSvg);
}
}
15 changes: 15 additions & 0 deletions vendor/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
'App\\Application' => $baseDir . '/app/Application.php',
'App\\Console\\Commands\\RegisterApp' => $baseDir . '/app/Console/Commands/RegisterApp.php',
'App\\EnhancedApps' => $baseDir . '/app/EnhancedApps.php',
'App\\Facades\\Form' => $baseDir . '/app/Facades/Form.php',
'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => $baseDir . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
'App\\Http\\Controllers\\Auth\\LoginController' => $baseDir . '/app/Http/Controllers/Auth/LoginController.php',
'App\\Http\\Controllers\\Auth\\RegisterController' => $baseDir . '/app/Http/Controllers/Auth/RegisterController.php',
Expand All @@ -57,10 +58,13 @@
'App\\Providers\\RouteServiceProvider' => $baseDir . '/app/Providers/RouteServiceProvider.php',
'App\\Search' => $baseDir . '/app/Search.php',
'App\\SearchInterface' => $baseDir . '/app/SearchInterface.php',
'App\\Services\\CustomFormBuilder' => $baseDir . '/app/Services/CustomFormBuilder.php',
'App\\Setting' => $baseDir . '/app/Setting.php',
'App\\SettingGroup' => $baseDir . '/app/SettingGroup.php',
'App\\SettingUser' => $baseDir . '/app/SettingUser.php',
'App\\SupportedApps' => $baseDir . '/app/SupportedApps.php',
'App\\SupportedApps\\Nzbget\\Nzbget' => $baseDir . '/app/SupportedApps/Nzbget/Nzbget.php',
'App\\SupportedApps\\SABnzbd\\SABnzbd' => $baseDir . '/app/SupportedApps/SABnzbd/SABnzbd.php',
'App\\User' => $baseDir . '/app/User.php',
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Aws\\ACMPCA\\ACMPCAClient' => $vendorDir . '/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php',
Expand Down Expand Up @@ -8103,6 +8107,17 @@
'Whoops\\Util\\Misc' => $vendorDir . '/filp/whoops/src/Whoops/Util/Misc.php',
'Whoops\\Util\\SystemFacade' => $vendorDir . '/filp/whoops/src/Whoops/Util/SystemFacade.php',
'Whoops\\Util\\TemplateHelper' => $vendorDir . '/filp/whoops/src/Whoops/Util/TemplateHelper.php',
'enshrined\\svgSanitize\\ElementReference\\Resolver' => $vendorDir . '/enshrined/svg-sanitize/src/ElementReference/Resolver.php',
'enshrined\\svgSanitize\\ElementReference\\Subject' => $vendorDir . '/enshrined/svg-sanitize/src/ElementReference/Subject.php',
'enshrined\\svgSanitize\\ElementReference\\Usage' => $vendorDir . '/enshrined/svg-sanitize/src/ElementReference/Usage.php',
'enshrined\\svgSanitize\\Exceptions\\NestingException' => $vendorDir . '/enshrined/svg-sanitize/src/Exceptions/NestingException.php',
'enshrined\\svgSanitize\\Helper' => $vendorDir . '/enshrined/svg-sanitize/src/Helper.php',
'enshrined\\svgSanitize\\Sanitizer' => $vendorDir . '/enshrined/svg-sanitize/src/Sanitizer.php',
'enshrined\\svgSanitize\\data\\AllowedAttributes' => $vendorDir . '/enshrined/svg-sanitize/src/data/AllowedAttributes.php',
'enshrined\\svgSanitize\\data\\AllowedTags' => $vendorDir . '/enshrined/svg-sanitize/src/data/AllowedTags.php',
'enshrined\\svgSanitize\\data\\AttributeInterface' => $vendorDir . '/enshrined/svg-sanitize/src/data/AttributeInterface.php',
'enshrined\\svgSanitize\\data\\TagInterface' => $vendorDir . '/enshrined/svg-sanitize/src/data/TagInterface.php',
'enshrined\\svgSanitize\\data\\XPath' => $vendorDir . '/enshrined/svg-sanitize/src/data/XPath.php',
'voku\\helper\\ASCII' => $vendorDir . '/voku/portable-ascii/src/voku/helper/ASCII.php',
'�' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php',
);
1 change: 1 addition & 0 deletions vendor/composer/autoload_psr4.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

return array(
'voku\\' => array($vendorDir . '/voku/portable-ascii/src/voku'),
'enshrined\\svgSanitize\\' => array($vendorDir . '/enshrined/svg-sanitize/src'),
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),
Expand Down
23 changes: 23 additions & 0 deletions vendor/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
array (
'voku\\' => 5,
),
'e' =>
array (
'enshrined\\svgSanitize\\' => 22,
),
'W' =>
array (
'Whoops\\' => 7,
Expand Down Expand Up @@ -227,6 +231,10 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
array (
0 => __DIR__ . '/..' . '/voku/portable-ascii/src/voku',
),
'enshrined\\svgSanitize\\' =>
array (
0 => __DIR__ . '/..' . '/enshrined/svg-sanitize/src',
),
'Whoops\\' =>
array (
0 => __DIR__ . '/..' . '/filp/whoops/src/Whoops',
Expand Down Expand Up @@ -720,6 +728,7 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
'App\\Application' => __DIR__ . '/../..' . '/app/Application.php',
'App\\Console\\Commands\\RegisterApp' => __DIR__ . '/../..' . '/app/Console/Commands/RegisterApp.php',
'App\\EnhancedApps' => __DIR__ . '/../..' . '/app/EnhancedApps.php',
'App\\Facades\\Form' => __DIR__ . '/../..' . '/app/Facades/Form.php',
'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php',
'App\\Http\\Controllers\\Auth\\LoginController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php',
'App\\Http\\Controllers\\Auth\\RegisterController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php',
Expand All @@ -745,10 +754,13 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
'App\\Providers\\RouteServiceProvider' => __DIR__ . '/../..' . '/app/Providers/RouteServiceProvider.php',
'App\\Search' => __DIR__ . '/../..' . '/app/Search.php',
'App\\SearchInterface' => __DIR__ . '/../..' . '/app/SearchInterface.php',
'App\\Services\\CustomFormBuilder' => __DIR__ . '/../..' . '/app/Services/CustomFormBuilder.php',
'App\\Setting' => __DIR__ . '/../..' . '/app/Setting.php',
'App\\SettingGroup' => __DIR__ . '/../..' . '/app/SettingGroup.php',
'App\\SettingUser' => __DIR__ . '/../..' . '/app/SettingUser.php',
'App\\SupportedApps' => __DIR__ . '/../..' . '/app/SupportedApps.php',
'App\\SupportedApps\\Nzbget\\Nzbget' => __DIR__ . '/../..' . '/app/SupportedApps/Nzbget/Nzbget.php',
'App\\SupportedApps\\SABnzbd\\SABnzbd' => __DIR__ . '/../..' . '/app/SupportedApps/SABnzbd/SABnzbd.php',
'App\\User' => __DIR__ . '/../..' . '/app/User.php',
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Aws\\ACMPCA\\ACMPCAClient' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php',
Expand Down Expand Up @@ -8791,6 +8803,17 @@ class ComposerStaticInitb2555e5ff7197b9e020da74bbd3b7cfa
'Whoops\\Util\\Misc' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Util/Misc.php',
'Whoops\\Util\\SystemFacade' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Util/SystemFacade.php',
'Whoops\\Util\\TemplateHelper' => __DIR__ . '/..' . '/filp/whoops/src/Whoops/Util/TemplateHelper.php',
'enshrined\\svgSanitize\\ElementReference\\Resolver' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/ElementReference/Resolver.php',
'enshrined\\svgSanitize\\ElementReference\\Subject' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/ElementReference/Subject.php',
'enshrined\\svgSanitize\\ElementReference\\Usage' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/ElementReference/Usage.php',
'enshrined\\svgSanitize\\Exceptions\\NestingException' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/Exceptions/NestingException.php',
'enshrined\\svgSanitize\\Helper' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/Helper.php',
'enshrined\\svgSanitize\\Sanitizer' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/Sanitizer.php',
'enshrined\\svgSanitize\\data\\AllowedAttributes' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/data/AllowedAttributes.php',
'enshrined\\svgSanitize\\data\\AllowedTags' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/data/AllowedTags.php',
'enshrined\\svgSanitize\\data\\AttributeInterface' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/data/AttributeInterface.php',
'enshrined\\svgSanitize\\data\\TagInterface' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/data/TagInterface.php',
'enshrined\\svgSanitize\\data\\XPath' => __DIR__ . '/..' . '/enshrined/svg-sanitize/src/data/XPath.php',
'voku\\helper\\ASCII' => __DIR__ . '/..' . '/voku/portable-ascii/src/voku/helper/ASCII.php',
'�' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php',
);
Expand Down
Loading