This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

@@ -4,6 +4,7 @@ namespace Illuminate\Routing\Middleware;
use Closure;
use Illuminate\Contracts\Routing\Registrar;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class SubstituteBindings
{
@@ -34,9 +35,17 @@ class SubstituteBindings
*/
public function handle($request, Closure $next)
{
$this->router->substituteBindings($route = $request->route());
try {
$this->router->substituteBindings($route = $request->route());
$this->router->substituteImplicitBindings($route);
$this->router->substituteImplicitBindings($route);
} catch (ModelNotFoundException $exception) {
if ($route->getMissing()) {
return $route->getMissing()($request, $exception);
}
throw $exception;
}
return $next($request);
}

View File

@@ -4,7 +4,10 @@ namespace Illuminate\Routing\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Cache\RateLimiting\Unlimited;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Support\Arr;
use Illuminate\Support\InteractsWithTime;
use Illuminate\Support\Str;
use RuntimeException;
@@ -46,22 +49,92 @@ class ThrottleRequests
*/
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1, $prefix = '')
{
$key = $prefix.$this->resolveRequestSignature($request);
$maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts);
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
throw $this->buildException($key, $maxAttempts);
if (is_string($maxAttempts)
&& func_num_args() === 3
&& ! is_null($limiter = $this->limiter->limiter($maxAttempts))) {
return $this->handleRequestUsingNamedLimiter($request, $next, $maxAttempts, $limiter);
}
$this->limiter->hit($key, $decayMinutes * 60);
return $this->handleRequest(
$request,
$next,
[
(object) [
'key' => $prefix.$this->resolveRequestSignature($request),
'maxAttempts' => $this->resolveMaxAttempts($request, $maxAttempts),
'decayMinutes' => $decayMinutes,
'responseCallback' => null,
],
]
);
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $limiterName
* @param \Closure $limiter
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
*/
protected function handleRequestUsingNamedLimiter($request, Closure $next, $limiterName, Closure $limiter)
{
$limiterResponse = call_user_func($limiter, $request);
if ($limiterResponse instanceof Response) {
return $limiterResponse;
} elseif ($limiterResponse instanceof Unlimited) {
return $next($request);
}
return $this->handleRequest(
$request,
$next,
collect(Arr::wrap($limiterResponse))->map(function ($limit) use ($limiterName) {
return (object) [
'key' => md5($limiterName.$limit->key),
'maxAttempts' => $limit->maxAttempts,
'decayMinutes' => $limit->decayMinutes,
'responseCallback' => $limit->responseCallback,
];
})->all()
);
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param array $limits
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
*/
protected function handleRequest($request, Closure $next, array $limits)
{
foreach ($limits as $limit) {
if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) {
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
}
$this->limiter->hit($limit->key, $limit->decayMinutes * 60);
}
$response = $next($request);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts)
);
foreach ($limits as $limit) {
$response = $this->addHeaders(
$response,
$limit->maxAttempts,
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts)
);
}
return $response;
}
/**
@@ -96,9 +169,7 @@ class ThrottleRequests
{
if ($user = $request->user()) {
return sha1($user->getAuthIdentifier());
}
if ($route = $request->route()) {
} elseif ($route = $request->route()) {
return sha1($route->getDomain().'|'.$request->ip());
}
@@ -108,11 +179,13 @@ class ThrottleRequests
/**
* Create a 'too many attempts' exception.
*
* @param \Illuminate\Http\Request $request
* @param string $key
* @param int $maxAttempts
* @param callable|null $responseCallback
* @return \Illuminate\Http\Exceptions\ThrottleRequestsException
*/
protected function buildException($key, $maxAttempts)
protected function buildException($request, $key, $maxAttempts, $responseCallback = null)
{
$retryAfter = $this->getTimeUntilNextRetry($key);
@@ -122,9 +195,9 @@ class ThrottleRequests
$retryAfter
);
return new ThrottleRequestsException(
'Too Many Attempts.', null, $headers
);
return is_callable($responseCallback)
? new HttpResponseException($responseCallback($request, $headers))
: new ThrottleRequestsException('Too Many Attempts.', null, $headers);
}
/**
@@ -150,7 +223,7 @@ class ThrottleRequests
protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
{
$response->headers->add(
$this->getHeaders($maxAttempts, $remainingAttempts, $retryAfter)
$this->getHeaders($maxAttempts, $remainingAttempts, $retryAfter, $response)
);
return $response;
@@ -162,10 +235,20 @@ class ThrottleRequests
* @param int $maxAttempts
* @param int $remainingAttempts
* @param int|null $retryAfter
* @param \Symfony\Component\HttpFoundation\Response|null $response
* @return array
*/
protected function getHeaders($maxAttempts, $remainingAttempts, $retryAfter = null)
protected function getHeaders($maxAttempts,
$remainingAttempts,
$retryAfter = null,
?Response $response = null)
{
if ($response &&
! is_null($response->headers->get('X-RateLimit-Remaining')) &&
(int) $response->headers->get('X-RateLimit-Remaining') <= (int) $remainingAttempts) {
return [];
}
$headers = [
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $remainingAttempts,
@@ -189,10 +272,6 @@ class ThrottleRequests
*/
protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
{
if (is_null($retryAfter)) {
return $this->limiter->retriesLeft($key, $maxAttempts);
}
return 0;
return is_null($retryAfter) ? $this->limiter->retriesLeft($key, $maxAttempts) : 0;
}
}

View File

@@ -3,6 +3,7 @@
namespace Illuminate\Routing\Middleware;
use Closure;
use Illuminate\Cache\RateLimiter;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Redis\Limiters\DurationLimiter;
@@ -16,27 +17,30 @@ class ThrottleRequestsWithRedis extends ThrottleRequests
protected $redis;
/**
* The timestamp of the end of the current duration.
* The timestamp of the end of the current duration by key.
*
* @var int
* @var array
*/
public $decaysAt;
public $decaysAt = [];
/**
* The number of remaining slots.
* The number of remaining slots by key.
*
* @var int
* @var array
*/
public $remaining;
public $remaining = [];
/**
* Create a new request throttler.
*
* @param \Illuminate\Cache\RateLimiter $limiter
* @param \Illuminate\Contracts\Redis\Factory $redis
* @return void
*/
public function __construct(Redis $redis)
public function __construct(RateLimiter $limiter, Redis $redis)
{
parent::__construct($limiter);
$this->redis = $redis;
}
@@ -45,29 +49,30 @@ class ThrottleRequestsWithRedis extends ThrottleRequests
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param int|string $maxAttempts
* @param float|int $decayMinutes
* @param string $prefix
* @return mixed
* @param array $limits
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
* @throws \Illuminate\Http\Exceptions\ThrottleRequestsException
*/
public function handle($request, Closure $next, $maxAttempts = 60, $decayMinutes = 1, $prefix = '')
protected function handleRequest($request, Closure $next, array $limits)
{
$key = $prefix.$this->resolveRequestSignature($request);
$maxAttempts = $this->resolveMaxAttempts($request, $maxAttempts);
if ($this->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {
throw $this->buildException($key, $maxAttempts);
foreach ($limits as $limit) {
if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decayMinutes)) {
throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback);
}
}
$response = $next($request);
return $this->addHeaders(
$response, $maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts)
);
foreach ($limits as $limit) {
$response = $this->addHeaders(
$response,
$limit->maxAttempts,
$this->calculateRemainingAttempts($limit->key, $limit->maxAttempts)
);
}
return $response;
}
/**
@@ -84,8 +89,8 @@ class ThrottleRequestsWithRedis extends ThrottleRequests
$this->redis, $key, $maxAttempts, $decayMinutes * 60
);
return tap(! $limiter->acquire(), function () use ($limiter) {
[$this->decaysAt, $this->remaining] = [
return tap(! $limiter->acquire(), function () use ($key, $limiter) {
[$this->decaysAt[$key], $this->remaining[$key]] = [
$limiter->decaysAt, $limiter->remaining,
];
});
@@ -101,11 +106,7 @@ class ThrottleRequestsWithRedis extends ThrottleRequests
*/
protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
{
if (is_null($retryAfter)) {
return $this->remaining;
}
return 0;
return is_null($retryAfter) ? $this->remaining[$key] : 0;
}
/**
@@ -116,6 +117,6 @@ class ThrottleRequestsWithRedis extends ThrottleRequests
*/
protected function getTimeUntilNextRetry($key)
{
return $this->decaysAt - $this->currentTime();
return $this->decaysAt[$key] - $this->currentTime();
}
}

View File

@@ -12,13 +12,14 @@ class ValidateSignature
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $relative
* @return \Illuminate\Http\Response
*
* @throws \Illuminate\Routing\Exceptions\InvalidSignatureException
*/
public function handle($request, Closure $next)
public function handle($request, Closure $next, $relative = null)
{
if ($request->hasValidSignature()) {
if ($request->hasValidSignature($relative !== 'relative')) {
return $next($request);
}