Skip to content

Commit e2326e6

Browse files
authored
Feature/fil001 141 redirect url validation (#15)
* Add url validation rules * Redirect regardless of protocol of url * Test the protocol redirect adjustments
1 parent bea01a6 commit e2326e6

File tree

4 files changed

+128
-11
lines changed

4 files changed

+128
-11
lines changed

src/Filament/RedirectResource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ public static function form(Form $form): Form
2424
return $form
2525
->schema([
2626
Forms\Components\TextInput::make('from')
27+
->rules(['url', 'required'])
2728
->required(),
2829

2930
Forms\Components\TextInput::make('to')
31+
->rules(['url', 'required'])
3032
->required(),
3133

3234
Forms\Components\Select::make('status')

src/Http/Middleware/Redirects.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,32 @@ public function handle(Request $request, Closure $next)
3030
$uri = Str::ascii($uri);
3131
$requestUri = Str::ascii($requestUri);
3232

33+
$uriWithoutProtocol = Str::after($uri, '://');
34+
3335
$current = [
3436
'full' => $uri,
3537
'fullNoQuery' => Str::beforeLast($uri, '?'),
3638
'fullWithTrailingSlash' => Str::finish($uri, '/'),
3739
'fullWithoutTrailingSlash' => Str::replaceEnd('/', '', $uri),
40+
'fullWithoutProtocol' => $uriWithoutProtocol,
41+
'fullWithoutProtocolNoQuery' => Str::beforeLast($uriWithoutProtocol, '?'),
3842
'path' => $requestUri,
3943
'pathNoQuery' => Str::beforeLast($requestUri, '?'),
4044
];
4145

4246
$activeRedirect = $urlMaps->first(function ($redirect) use ($current) {
4347
$from = $redirect->clean_from;
48+
$fromWithoutProtocol = preg_replace('~^https?://~', '', $from);
4449

4550
$hasWildcard = Str::contains($from, config('filament-redirects.route-wildcard', '*'));
4651

4752
return
4853
($hasWildcard && Str::is($from, $current['path'])) ||
4954
($hasWildcard && Str::is($from, $current['full'])) ||
50-
(in_array($from, $current));
55+
($hasWildcard && Str::is($fromWithoutProtocol, $current['fullWithoutProtocol'])) ||
56+
(in_array($from, $current)) ||
57+
($fromWithoutProtocol === $current['fullWithoutProtocol']) ||
58+
($fromWithoutProtocol === $current['fullWithoutProtocolNoQuery']);
5159
});
5260

5361
if (! $activeRedirect || $activeRedirect->clean_from === $activeRedirect->to) {

tests/Feature/Filament/RedirectResource/Pages/ManageRedirectsTest.php

Lines changed: 117 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
<?php
22

33
use Codedor\FilamentRedirects\Filament\RedirectResource\Pages\ManageRedirects;
4+
use Codedor\FilamentRedirects\Http\Middleware\Redirects;
45
use Codedor\FilamentRedirects\Models\Redirect;
56
use Filament\Notifications\Notification;
7+
use Illuminate\Http\Request;
68
use Illuminate\Support\Facades\Storage;
79

810
use function Pest\Livewire\livewire;
911

1012
beforeEach(function () {
1113
$this->redirects = Redirect::factory()->createMany([
1214
[
13-
'from' => '/one',
14-
'to' => '/two',
15+
'from' => 'http://example.com/one',
16+
'to' => 'http://example.com/two',
1517
],
1618
[
17-
'from' => '/foo',
18-
'to' => '/bar',
19+
'from' => 'https://example.com/foo',
20+
'to' => 'https://example.com/bar',
1921
],
2022
]);
2123

@@ -66,8 +68,8 @@
6668

6769
$this->assertDatabaseCount(Redirect::class, 3);
6870
$this->assertDatabaseHas(Redirect::class, [
69-
'from' => '/from',
70-
'to' => '/to',
71+
'from' => 'https://example.com/from',
72+
'to' => 'https://example.com/to',
7173
'status' => 301,
7274
]);
7375
});
@@ -85,16 +87,121 @@
8587
livewire(ManageRedirects::class)
8688
->assertActionExists('create')
8789
->callAction('create', [
88-
'from' => '/from',
89-
'to' => '/to',
90+
'from' => 'https://example.com/from',
91+
'to' => 'https://example.com/to',
9092
'status' => 410,
9193
])
9294
->assertHasNoActionErrors();
9395

9496
$this->assertDatabaseCount(Redirect::class, 3);
9597
$this->assertDatabaseHas(Redirect::class, [
96-
'from' => '/from',
97-
'to' => '/to',
98+
'from' => 'https://example.com/from',
99+
'to' => 'https://example.com/to',
98100
'status' => 410,
99101
]);
100102
});
103+
104+
it('can create a redirect with validation errors for invalid URLs', function () {
105+
livewire(ManageRedirects::class)
106+
->assertActionExists('create')
107+
->callAction('create', [
108+
'from' => 'invalid-url',
109+
'to' => 'another-invalid-url',
110+
])
111+
->assertHasActionErrors(['from' => 'url', 'to' => 'url']);
112+
});
113+
114+
it('can create a redirect with different protocols', function () {
115+
livewire(ManageRedirects::class)
116+
->assertActionExists('create')
117+
->callAction('create', [
118+
'from' => 'http://example.com/old',
119+
'to' => 'https://example.com/new',
120+
'status' => 301,
121+
])
122+
->assertHasNoActionErrors();
123+
124+
$this->assertDatabaseHas(Redirect::class, [
125+
'from' => 'http://example.com/old',
126+
'to' => 'https://example.com/new',
127+
'status' => 301,
128+
]);
129+
});
130+
131+
it('redirects correctly regardless of protocol', function ($fromProtocol, $toProtocol) {
132+
$redirect = Redirect::create([
133+
'from' => "{$fromProtocol}://example.com/test",
134+
'to' => "{$toProtocol}://example.com/result",
135+
'status' => 301,
136+
'online' => true,
137+
]);
138+
139+
$request = Request::create($redirect->from);
140+
141+
$middleware = new Redirects;
142+
$response = $middleware->handle($request, fn () => response('This is a secret place'));
143+
144+
expect($response->getStatusCode())
145+
->toBe(301)
146+
->and($response->headers->get('Location'))
147+
->toBe($redirect->to);
148+
149+
$redirect->from = $fromProtocol === 'http'
150+
? 'https://example.com/test'
151+
: 'http://example.com/test';
152+
153+
$request = Request::create($redirect->from);
154+
155+
$response = $middleware->handle($request, fn () => response('This is a secret place'));
156+
157+
expect($response->getStatusCode())
158+
->toBe(301)
159+
->and($response->headers->get('Location'))
160+
->toBe($redirect->to);
161+
})
162+
->with([
163+
'http to http' => ['http', 'http'],
164+
'http to https' => ['http', 'https'],
165+
'https to http' => ['https', 'http'],
166+
'https to https' => ['https', 'https'],
167+
]);
168+
169+
it('redirects correctly with query parameters', function () {
170+
$redirect = Redirect::create([
171+
'from' => 'http://example.com/query',
172+
'to' => 'https://example.com/result',
173+
'status' => 301,
174+
'pass_query_string' => true,
175+
'online' => true,
176+
]);
177+
178+
$query = '?param=value';
179+
$request = Request::create("{$redirect->from}{$query}", 'GET');
180+
181+
$middleware = new Redirects;
182+
$response = $middleware->handle($request, fn () => response('This is a secret place'));
183+
184+
expect($response->getStatusCode())
185+
->toBe(301)
186+
->and($response->headers->get('Location'))
187+
->toBe("{$redirect->to}{$query}");
188+
});
189+
190+
it('handles wildcard redirects', function () {
191+
$redirect = Redirect::create([
192+
'from' => 'http://example.com/wildcard*',
193+
'to' => 'https://example.com/result',
194+
'status' => 301,
195+
'online' => true,
196+
]);
197+
198+
$request = Request::create('http://example.com/wildcard-test', 'GET');
199+
200+
$middleware = new Redirects;
201+
$response = $middleware->handle($request, fn () => response('This is a secret place'));
202+
203+
expect($response->getStatusCode())
204+
->toBe(301)
205+
->and($response->headers->get('Location'))
206+
->toBe($redirect->to);
207+
});
594 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)