Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel - regex route match everything but not exactly one or more word

I make a route like

Route::get('/{url1}', function ($url1) {
    return ' url1: '.$url1;
})
->where('url1', '^(?!(string1|string2)$)');

and access url like:
- domain/abc not found => incorrect ??
- domain/string1 not found => correct

and more, when i do with

Route::get('/{url1}/{url2}', function ($url1) {
    return ' url1: '.$url1;
})
->where('url1', '^(?!(string1|string2)$)');

and access url like:
- domain/abc/abc not found => incorrect ???
- domain/string1/abc not found => correct

How to fix that thank

like image 472
DeLe Avatar asked Jan 01 '20 04:01

DeLe


5 Answers

Please try with this for 1st scenario:

Route::get('/{url1}', function ($url1) {
    return ' url1: '.$url1;
})->where('url1', '^!(string1|string2)$');

for 2nd scenario:

Route::get('/{url1}/{url2}', function ($url1, $url2) {
    return ' url1: '.$url1 . ' url2: '.$url2;
})->where('url1', '^!(string1|string2)$');

i hope this help :)

like image 54
Mitesh Rathod Avatar answered Oct 23 '22 00:10

Mitesh Rathod


Try this

 Route::get('{url1}', function ($url1) {
    return ' url1: '.$url1;
})->where('url1','^(?!string1$|string2$)([a-zA-Z0-9-]+)');

Unclean way to achieve this

Route::get('{url1}/{url2?}', function ($url1,$url2 = null) {
        if ($url2 == "string1" || $url2 == "string2" || $url1 == "string1" || $url1 == "string2") {
            return "false";
        } else {
            return "true";
        }
});

One more way I have tried in by using RouteServiceProvider.php

Change your boot() like this.

public function boot()
    {
        //
        Route::pattern('url1', '^(?!string1$|string2$)([a-zA-Z0-9-]+)');
        Route::pattern('url2', '^(?!string1$|string2$)([a-zA-Z0-9-]+)');
        parent::boot();
    }
like image 3
Prashant Deshmukh..... Avatar answered Oct 22 '22 23:10

Prashant Deshmukh.....


After tests I think it's impossible to achieve exactly what you want. It seems when you want to exclude string1 and string2 you need to agree that also strings starting with string1 and string2 will be excluded (so for example string1aaa).

When using such routes:

Route::get('/{url1}', function ($url1) {
    return ' url1: '.$url1;
})->where('url1', '(?!string1|string2)[^\/]+');


Route::get('/{url1}/{url2}', function ($url1, $url2) {
    return ' url1: '.$url1. ' # '.$url2;
})->where('url1', '(?!string1|string2)[^\/]+');

result will be:

domain/abc - found, correct
domain/string1 - not found, correct
domain/abc/abc - found, correct
domain/string1/abc - not found, correct
domain/string1aaa - not found, I believe you need to accept this
domain/string1aaa/abc - not found, I believe you need to accept this

I believe such limitation comes from Laravel and not from regex. If you need to accept also parameters starting with string1 and string2 I believe you need to do it in manual way like so:

Route::get('/{url1}', function ($url1) {
    if (!preg_match('#^(?!string1$|string2$)[^\/]*$#', $url1)) {
        abort(404);
    }

    return ' url1: '.$url1;
});


Route::get('/{url1}/{url2}', function ($url1, $url2) {
    if (!preg_match('#^(?!string1$|string2$)[^\/]*$#', $url1)) {
        abort(404);
    }

    return ' url1: '.$url1. ' # '.$url2;
});
like image 3
Marcin Nabiałek Avatar answered Oct 22 '22 22:10

Marcin Nabiałek


Try this regex:

    Route::get('/{url1}', function ($url1) {
        return 'url: '.url1;
    })->where('url1', '^(?!(string1|string2)$)(\S+)');
like image 1
TsaiKoga Avatar answered Oct 22 '22 22:10

TsaiKoga


Rather than writing a route to match anything other than certain static strings, I find it clearer to write two routes: one route to match certain static strings, and another route to match everything else.

// route that matches forbidden static strings, optionally with a postfix slug
$router->get('/{forbidden}/{optional_path?}', function () {
    return response('Not found', 404);
})->where([ 'forbidden' => '(?:string1|string2)', 'optional_path' => '.*' ]);

// route that matches anything else (order of definition matters, must be last)
// might also consider using Route::fallback(), but I prefer to leave that
// alone in case my future self changes this below and opens up a hole
$router->get('/{anything?}', function () {
    return response('Found', 200);
})->where([ 'anything' => '.*' ]);

Which results in*:

  • domain => 200 Found
  • domain/ => 200 Found
  • domain/abc => 200 Found
  • domain/string1 => 404 Not found
  • domain/string1/ => 404 Not found
  • domain/string1/abc => 404 Not found
  • domain/string10 => 200 Found
  • domain/string10/ => 200 Found
  • domain/string10/abc => 200 Found
  • domain/string2 => 404 Not found
  • domain/string2/ => 404 Not found
  • domain/string2/abc => 404 Not found
  • domain/string20 => 200 Found
  • domain/string20/ => 200 Found
  • domain/string20/abc => 200 Found

I find this more clear, because I don't have to think in terms of exclusions. Rather, I can think of matching exactly what I want to forbid, then letting Laravel react to everything else (fail open policy). This may not meet your design criteria, but I do believe it results in clearer code.

Also, the code is more performant. ?! has to backtrack, which is by definition more expensive than forward matching.

I don't have a Laravel environment to hand, but I'll hazard a guess as to why your attempts didn't work. Laravel uses Symfony Router, which does not support lookarounds on slugs. IIRC, when a lookaround's detected, Symfony applies the lookaround to the entire URL, not the slug you've bound the pattern to. This messes with the developer's idea of how anchors (^, $) and greedy (*) meta characters work. This can lead to a bad experience in trying to get it to work, since the developer's operating under one assumption but the underlying libraries operating on another.


* Full disclosure, I wrote this for Lumen then mentally converted it to Laravel format. It's possible there are some translation errors. Here's the original Lumen:

$router->get('/{forbidden:(?:string1|string2)}[/{optional_path:.*}]', function () {
    return response('Not found', 404);
});
$router->get('{anything:.*}', function () {                                   
    return response('Found', 200);
});
like image 1
bishop Avatar answered Oct 23 '22 00:10

bishop