@@ -145,17 +145,24 @@ protected function getCacheKey(string $scope, string $value): string
145
145
if (!isset ($ this ->cacheKeys [$ scope ][$ value ])) {
146
146
switch ($ scope ) {
147
147
case Constants::SCOPE_RANGE :
148
- $ this -> cacheKeys [ $ scope ][ $ value ] = Constants::SCOPE_IP . self ::CACHE_SEP . $ value ;
148
+ $ result = Constants::SCOPE_IP . self ::CACHE_SEP . $ value ;
149
149
break ;
150
150
case Constants::SCOPE_IP :
151
151
case Constants::CACHE_TAG_GEO . self ::CACHE_SEP . Constants::SCOPE_IP :
152
152
case Constants::CACHE_TAG_CAPTCHA . self ::CACHE_SEP . Constants::SCOPE_IP :
153
153
case Constants::SCOPE_COUNTRY :
154
- $ this -> cacheKeys [ $ scope ][ $ value ] = $ scope . self ::CACHE_SEP . $ value ;
154
+ $ result = $ scope . self ::CACHE_SEP . $ value ;
155
155
break ;
156
156
default :
157
157
throw new BouncerException ('Unknown scope: ' . $ scope );
158
158
}
159
+
160
+ /**
161
+ * Replace unauthorized symbols.
162
+ *
163
+ * @see https://symfony.com/doc/current/components/cache/cache_items.html#cache-item-keys-and-values
164
+ */
165
+ $ this ->cacheKeys [$ scope ][$ value ] = preg_replace ('/[^A-Za-z0-9_.]/ ' , self ::CACHE_SEP , $ result );
159
166
}
160
167
161
168
return $ this ->cacheKeys [$ scope ][$ value ];
@@ -285,7 +292,8 @@ protected function addRemediationToCacheItem(
285
292
]; // erase previous decision with the same id
286
293
287
294
// Build the item lifetime in cache and sort remediations by priority
288
- $ maxLifetime = max (array_column ($ remediations , 1 ));
295
+ $ exps = array_column ($ remediations , 1 );
296
+ $ maxLifetime = $ exps ? max ($ exps ) : 0 ;
289
297
$ prioritizedRemediations = Remediation::sortRemediationByPriority ($ remediations );
290
298
291
299
$ item ->set ($ prioritizedRemediations );
@@ -341,20 +349,14 @@ protected function defferUpdateCacheConfig(array $config): void
341
349
protected function formatRemediationFromDecision (?array $ decision ): array
342
350
{
343
351
if (!$ decision ) {
344
- /**
345
- * In stream mode we consider a clean IP forever... until the next resync.
346
- * in this case, forever is 10 years as PHP_INT_MAX will cause trouble with the Memcached Adapter
347
- * (int to float unwanted conversion)
348
- *
349
- */
350
- $ duration = $ this ->streamMode ? 315360000 : $ this ->cacheExpirationForCleanIp ;
352
+ $ duration = $ this ->cacheExpirationForCleanIp ;
351
353
352
354
return [Constants::REMEDIATION_BYPASS , time () + $ duration , 0 ];
353
355
}
354
356
355
357
$ duration = self ::parseDurationToSeconds ($ decision ['duration ' ]);
356
358
357
- // Don't set a max duration in stream mode to avoid bugs. Only the stream update has to change the cache state.
359
+ // In stream mode, only the stream update has to change the cache state.
358
360
if (!$ this ->streamMode ) {
359
361
$ duration = min ($ this ->cacheExpirationForBadIp , $ duration );
360
362
}
@@ -425,6 +427,10 @@ protected function miss(string $value, string $cacheScope): string
425
427
]);
426
428
}
427
429
}
430
+ // In stream mode, we do not save bypass decision in cache
431
+ if ($ this ->streamMode && !$ decisions ) {
432
+ return Constants::REMEDIATION_BYPASS ;
433
+ }
428
434
429
435
return $ this ->saveRemediationsForCacheKey ($ decisions , $ cacheKey );
430
436
}
@@ -464,7 +470,8 @@ protected function removeDecisionFromRemediationItem(string $cacheKey, int $deci
464
470
return true ;
465
471
}
466
472
// Build the item lifetime in cache and sort remediations by priority
467
- $ maxLifetime = max (array_column ($ remediations , 1 ));
473
+ $ exps = array_column ($ remediations , 1 );
474
+ $ maxLifetime = $ exps ? max ($ exps ) : 0 ;
468
475
$ cacheContent = Remediation::sortRemediationByPriority ($ remediations );
469
476
$ item ->expiresAt (new DateTime ('@ ' . $ maxLifetime ));
470
477
$ item ->set ($ cacheContent );
@@ -576,7 +583,8 @@ private function configureAdapter(): void
576
583
$ this ->adapter = new RedisTagAwareAdapter ((RedisAdapter::createConnection ($ redisDsn )));
577
584
} catch (Exception $ e ) {
578
585
throw new BouncerException ('Error when connecting to Redis. ' .
579
- ' Please fix the Redis DSN or select another cache technology. ' );
586
+ ' Please fix the Redis DSN or select another cache technology. ' .
587
+ ' Initial error was: ' . $ e ->getMessage ());
580
588
}
581
589
break ;
582
590
@@ -612,7 +620,7 @@ private static function parseDurationToSeconds(string $duration): int
612
620
$ re = '/(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m ' ;
613
621
preg_match ($ re , $ duration , $ matches );
614
622
if (!\count ($ matches )) {
615
- throw new BouncerException (" Unable to parse the following duration: $ { $ duration} . " );
623
+ throw new BouncerException (' Unable to parse the following duration: ' . $ duration );
616
624
}
617
625
$ seconds = 0 ;
618
626
if (isset ($ matches [2 ])) {
@@ -621,12 +629,14 @@ private static function parseDurationToSeconds(string $duration): int
621
629
if (isset ($ matches [3 ])) {
622
630
$ seconds += ((int )$ matches [3 ]) * 60 ; // minutes
623
631
}
632
+ $ secondsPart = 0 ;
624
633
if (isset ($ matches [4 ])) {
625
- $ seconds += ((int )$ matches [4 ]); // seconds
634
+ $ secondsPart += ((int )$ matches [4 ]); // seconds
626
635
}
627
636
if ('m ' === ($ matches [5 ])) { // units in milliseconds
628
- $ seconds *= 0.001 ;
637
+ $ secondsPart *= 0.001 ;
629
638
}
639
+ $ seconds += $ secondsPart ;
630
640
if ('- ' === ($ matches [1 ])) { // negative
631
641
$ seconds *= -1 ;
632
642
}
0 commit comments