mirror of
https://github.com/HDInnovations/UNIT3D-Community-Edition.git
synced 2026-04-21 09:20:08 -05:00
fix: whitelisted image url domain matching
Use wildcards on the full url instead of just the hostname.
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\WhitelistedImageDomain;
|
||||
use App\Models\WhitelistedImageUrl;
|
||||
|
||||
class Bbcode
|
||||
{
|
||||
@@ -496,15 +496,16 @@ class Bbcode
|
||||
}
|
||||
|
||||
if ($isImage) {
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$whitelistedImageUrls = cache()->rememberForever(
|
||||
'whitelisted-image-urls',
|
||||
fn () => WhitelistedImageUrl::query()->pluck('pattern'),
|
||||
);
|
||||
|
||||
if (!\is_string($host)) {
|
||||
return 'Broken link';
|
||||
}
|
||||
$isWhitelisted = $whitelistedImageUrls->contains(function (string $pattern) use ($url) {
|
||||
$pattern = str_replace('\*', '.*', preg_quote($pattern, '/'));
|
||||
|
||||
$whitelistedImageDomains = cache()->rememberForever('whitelisted-image-domains', fn () => WhitelistedImageDomain::query()->pluck('domain'));
|
||||
|
||||
$isWhitelisted = $whitelistedImageDomains->firstWhere(fn ($domain) => str_ends_with($host, $domain)) !== null;
|
||||
return preg_match('/^'.$pattern.'$/i', $url);
|
||||
});
|
||||
|
||||
if (!$isWhitelisted) {
|
||||
$url = 'https://wsrv.nl/?url='.urlencode($url);
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author Roardom <roardom@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Staff;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Staff\StoreWhitelistedImageDomainRequest;
|
||||
use App\Http\Requests\Staff\UpdateWhitelistedImageDomainRequest;
|
||||
use App\Models\WhitelistedImageDomain;
|
||||
|
||||
class WhitelistedImageDomainController extends Controller
|
||||
{
|
||||
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
{
|
||||
return view('Staff.whitelisted-image-domain.index', [
|
||||
'whitelistedImageDomains' => WhitelistedImageDomain::orderBy('domain')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateWhitelistedImageDomainRequest $request, WhitelistedImageDomain $whitelistedImageDomain): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$whitelistedImageDomain->update($request->validated());
|
||||
|
||||
cache()->forget('whitelisted-image-domains');
|
||||
|
||||
return to_route('staff.whitelisted_image_domains.index')
|
||||
->withSuccess('Domain updated successfully.');
|
||||
}
|
||||
|
||||
public function store(StoreWhitelistedImageDomainRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
WhitelistedImageDomain::create($request->validated());
|
||||
|
||||
cache()->forget('whitelisted-image-domains');
|
||||
|
||||
return to_route('staff.whitelisted_image_domains.index')
|
||||
->withSuccess('New image domain whitelisted.');
|
||||
}
|
||||
|
||||
public function destroy(WhitelistedImageDomain $whitelistedImageDomain): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$whitelistedImageDomain->delete();
|
||||
|
||||
cache()->forget('whitelisted-image-domains');
|
||||
|
||||
return to_route('staff.whitelisted_image_domains.index')
|
||||
->withSuccess('Domain removed from whitelist.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author Roardom <roardom@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Staff;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Staff\StoreWhitelistedImageUrlRequest;
|
||||
use App\Http\Requests\Staff\UpdateWhitelistedImageUrlRequest;
|
||||
use App\Models\WhitelistedImageUrl;
|
||||
|
||||
class WhitelistedImageUrlController extends Controller
|
||||
{
|
||||
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
{
|
||||
return view('Staff.whitelisted-image-url.index', [
|
||||
'whitelistedImageUrls' => WhitelistedImageUrl::orderBy('pattern')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateWhitelistedImageUrlRequest $request, WhitelistedImageUrl $whitelistedImageUrl): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$whitelistedImageUrl->update($request->validated());
|
||||
|
||||
cache()->forget('whitelisted-image-urls');
|
||||
|
||||
return to_route('staff.whitelisted_image_urls.index')
|
||||
->withSuccess('Image url pattern updated successfully.');
|
||||
}
|
||||
|
||||
public function store(StoreWhitelistedImageUrlRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
WhitelistedImageUrl::create($request->validated());
|
||||
|
||||
cache()->forget('whitelisted-image-urls');
|
||||
|
||||
return to_route('staff.whitelisted_image_urls.index')
|
||||
->withSuccess('New image url pattern whitelisted.');
|
||||
}
|
||||
|
||||
public function destroy(WhitelistedImageUrl $whitelistedImageUrl): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$whitelistedImageUrl->delete();
|
||||
|
||||
cache()->forget('whitelisted-image-urls');
|
||||
|
||||
return to_route('staff.whitelisted_image_urls.index')
|
||||
->withSuccess('Image url pattern removed from whitelist.');
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -15,7 +15,7 @@ namespace App\Http\Requests\Staff;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreWhitelistedImageDomainRequest extends FormRequest
|
||||
class StoreWhitelistedImageUrlRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
@@ -25,11 +25,11 @@ class StoreWhitelistedImageDomainRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'domain' => [
|
||||
'pattern' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
'unique:whitelisted_image_domains',
|
||||
'unique:whitelisted_image_urls',
|
||||
],
|
||||
];
|
||||
}
|
||||
+3
-3
@@ -15,7 +15,7 @@ namespace App\Http\Requests\Staff;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateWhitelistedImageDomainRequest extends FormRequest
|
||||
class UpdateWhitelistedImageUrlRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
@@ -25,11 +25,11 @@ class UpdateWhitelistedImageDomainRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'domain' => [
|
||||
'pattern' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
'unique:whitelisted_image_domains',
|
||||
'unique:whitelisted_image_urls',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -18,12 +18,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* App\Models\WhitelistedImageDomain.
|
||||
* App\Models\WhitelistedImageUrl.
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $domain
|
||||
* @property string $pattern
|
||||
*/
|
||||
class WhitelistedImageDomain extends Model
|
||||
class WhitelistedImageUrl extends Model
|
||||
{
|
||||
use Auditable;
|
||||
use HasFactory;
|
||||
@@ -7,9 +7,9 @@ use Illuminate\Support\Facades\Schema;
|
||||
return new class () extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('whitelisted_image_domains', function (Blueprint $table): void {
|
||||
Schema::create('whitelisted_image_urls', function (Blueprint $table): void {
|
||||
$table->increments('id');
|
||||
$table->string('domain')->unique();
|
||||
$table->string('pattern')->unique();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
@@ -180,10 +180,10 @@
|
||||
<p class="form__group form__group--horizontal">
|
||||
<a
|
||||
class="form__button form__button--text"
|
||||
href="{{ route('staff.whitelisted_image_domains.index') }}"
|
||||
href="{{ route('staff.whitelisted_image_urls.index') }}"
|
||||
>
|
||||
<i class="{{ config('other.font-awesome') }} fa-globe"></i>
|
||||
Whitelisted Image Domains
|
||||
Whitelisted Image URLs
|
||||
</a>
|
||||
</p>
|
||||
<p class="form__group form__group--horizontal">
|
||||
|
||||
+34
-55
@@ -6,15 +6,15 @@
|
||||
{{ __('staff.staff-dashboard') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb--active">Whitelisted Image Domains</li>
|
||||
<li class="breadcrumb--active">Whitelisted Image URLs</li>
|
||||
@endsection
|
||||
|
||||
@section('page', 'page__whitelisted-image-domains--index')
|
||||
@section('page', 'page__whitelisted-image-urls--index')
|
||||
|
||||
@section('main')
|
||||
<section class="panelV2">
|
||||
<header class="panel__header">
|
||||
<h2 class="panel__heading">Whitelisted Image Domains</h2>
|
||||
<h2 class="panel__heading">Whitelisted Image URLs</h2>
|
||||
<div class="panel__actions">
|
||||
<div class="panel__action" x-data="dialog">
|
||||
<button class="form__button form__button--text" x-bind="showDialog">
|
||||
@@ -25,21 +25,21 @@
|
||||
<form
|
||||
class="dialog__form"
|
||||
method="POST"
|
||||
action="{{ route('staff.whitelisted_image_domains.store') }}"
|
||||
action="{{ route('staff.whitelisted_image_urls.store') }}"
|
||||
x-bind="dialogForm"
|
||||
>
|
||||
@csrf
|
||||
<p class="form__group">
|
||||
<input
|
||||
id="domain"
|
||||
id="pattern"
|
||||
class="form__text"
|
||||
name="domain"
|
||||
name="pattern"
|
||||
placeholder=" "
|
||||
required
|
||||
type="text"
|
||||
/>
|
||||
<label class="form__label form__label--floating" for="domain">
|
||||
Domain
|
||||
<label class="form__label form__label--floating" for="pattern">
|
||||
URL pattern
|
||||
</label>
|
||||
</p>
|
||||
<p class="form__group">
|
||||
@@ -63,30 +63,30 @@
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<th>ID</th>
|
||||
<th>Domain</th>
|
||||
<th>URL Pattern</th>
|
||||
<th>{{ __('common.created_at') }}</th>
|
||||
<th>{{ __('forum.updated-at') }}</th>
|
||||
<th>{{ __('common.actions') }}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($whitelistedImageDomains as $whitelistedImageDomain)
|
||||
@forelse ($whitelistedImageUrls as $whitelistedImageUrl)
|
||||
<tr>
|
||||
<td>{{ $whitelistedImageDomain->id }}</td>
|
||||
<td>{{ $whitelistedImageDomain->domain }}</td>
|
||||
<td>{{ $whitelistedImageUrl->id }}</td>
|
||||
<td>{{ $whitelistedImageUrl->pattern }}</td>
|
||||
<td>
|
||||
<time
|
||||
datetime="{{ $whitelistedImageDomain->created_at }}"
|
||||
title="{{ $whitelistedImageDomain->created_at }}"
|
||||
datetime="{{ $whitelistedImageUrl->created_at }}"
|
||||
title="{{ $whitelistedImageUrl->created_at }}"
|
||||
>
|
||||
{{ $whitelistedImageDomain->created_at }}
|
||||
{{ $whitelistedImageUrl->created_at }}
|
||||
</time>
|
||||
</td>
|
||||
<td>
|
||||
<time
|
||||
datetime="{{ $whitelistedImageDomain->updated_at }}"
|
||||
title="{{ $whitelistedImageDomain->updated_at }}"
|
||||
datetime="{{ $whitelistedImageUrl->updated_at }}"
|
||||
title="{{ $whitelistedImageUrl->updated_at }}"
|
||||
>
|
||||
{{ $whitelistedImageDomain->updated_at }}
|
||||
{{ $whitelistedImageUrl->updated_at }}
|
||||
</time>
|
||||
</td>
|
||||
<td>
|
||||
@@ -105,26 +105,26 @@
|
||||
<form
|
||||
class="dialog__form"
|
||||
method="POST"
|
||||
action="{{ route('staff.whitelisted_image_domains.update', ['whitelistedImageDomain' => $whitelistedImageDomain]) }}"
|
||||
action="{{ route('staff.whitelisted_image_urls.update', ['whitelistedImageUrl' => $whitelistedImageUrl]) }}"
|
||||
x-bind="dialogForm"
|
||||
>
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<p class="form__group">
|
||||
<input
|
||||
id="domain"
|
||||
id="pattern"
|
||||
class="form__text"
|
||||
name="domain"
|
||||
name="pattern"
|
||||
placeholder=" "
|
||||
required
|
||||
type="text"
|
||||
value="{{ $whitelistedImageDomain->domain }}"
|
||||
value="{{ $whitelistedImageUrl->pattern }}"
|
||||
/>
|
||||
<label
|
||||
class="form__label form__label--floating"
|
||||
for="domain"
|
||||
for="pattern"
|
||||
>
|
||||
{{ __('common.position') }}
|
||||
URL Pattern
|
||||
</label>
|
||||
</p>
|
||||
<p class="form__group">
|
||||
@@ -146,7 +146,7 @@
|
||||
</li>
|
||||
<li class="data-table__action">
|
||||
<form
|
||||
action="{{ route('staff.whitelisted_image_domains.destroy', ['whitelistedImageDomain' => $whitelistedImageDomain]) }}"
|
||||
action="{{ route('staff.whitelisted_image_urls.destroy', ['whitelistedImageUrl' => $whitelistedImageUrl]) }}"
|
||||
method="POST"
|
||||
x-data="confirmation"
|
||||
>
|
||||
@@ -155,7 +155,7 @@
|
||||
<button
|
||||
x-on:click.prevent="confirmAction"
|
||||
class="form__button form__button--text"
|
||||
data-b64-deletion-message="{{ base64_encode('Are you sure you want to remove this whitelisted image domain: ' . $whitelistedImageDomain->domain . '?') }}"
|
||||
data-b64-deletion-message="{{ base64_encode('Are you sure you want to remove this whitelisted image url: ' . $whitelistedImageUrl->pattern . '?') }}"
|
||||
>
|
||||
{{ __('common.delete') }}
|
||||
</button>
|
||||
@@ -166,7 +166,7 @@
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="2">No whitelisted image domains.</td>
|
||||
<td colspan="2">No whitelisted image urls.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
@@ -181,36 +181,15 @@
|
||||
<div class="panel__body">
|
||||
<p>
|
||||
When users add images via BBCode, other users will load the image on page load. This
|
||||
means whoever operates the image domain can view the connecting IPs. Therefore, all
|
||||
images entered via BBCode are proxied.
|
||||
means whoever operates the website of the image url can view the connecting IPs.
|
||||
Therefore, all images entered via BBCode are proxied.
|
||||
</p>
|
||||
<p>
|
||||
In exception cases where the proxy blocks a popular image host, that image host
|
||||
domain should be whitelisted here. Any trusted image domains can also be included
|
||||
here to increase client image loading speeds.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">Warning</h2>
|
||||
<div class="panel__body">
|
||||
<p>
|
||||
Note: if you whitelist
|
||||
<code>xyz.com</code>
|
||||
, it will also whitelist
|
||||
<code>abcxyz.com</code>
|
||||
since they share the same suffix. To prevent this, whitelist
|
||||
<code>https://xyz.com</code>
|
||||
instead.
|
||||
</p>
|
||||
<p>
|
||||
Alternatively, if you wish to allow all subdomains of
|
||||
<code>xyz.com</code>
|
||||
, e.g.
|
||||
<code>image1.xyz.com</code>
|
||||
, then whitelist
|
||||
<code>.xyz.com</code>
|
||||
.
|
||||
In exception cases where the proxy blocks a popular image host, that image url
|
||||
should be whitelisted here. Any trusted image urls can also be included here to
|
||||
increase client image loading speeds. You can use
|
||||
<code>*</code>
|
||||
as a wildcard when matching urls.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
+7
-7
@@ -1106,13 +1106,13 @@ Route::middleware('language')->group(function (): void {
|
||||
});
|
||||
});
|
||||
|
||||
// Whitelisted Image Domains
|
||||
Route::prefix('whitelisted-image-domains')->group(function (): void {
|
||||
Route::name('whitelisted_image_domains.')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'index'])->name('index');
|
||||
Route::post('/store', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'store'])->name('store');
|
||||
Route::patch('/{whitelistedImageDomain}/update', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'update'])->name('update');
|
||||
Route::delete('/{whitelistedImageDomain}/destroy', [App\Http\Controllers\Staff\WhitelistedImageDomainController::class, 'destroy'])->name('destroy');
|
||||
// Whitelisted Image URL Patterns
|
||||
Route::prefix('whitelisted-image-urls')->group(function (): void {
|
||||
Route::name('whitelisted_image_urls.')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'index'])->name('index');
|
||||
Route::post('/store', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'store'])->name('store');
|
||||
Route::patch('/{whitelistedImageUrl}/update', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'update'])->name('update');
|
||||
Route::delete('/{whitelistedImageUrl}/destroy', [App\Http\Controllers\Staff\WhitelistedImageUrlController::class, 'destroy'])->name('destroy');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user