@@ -145,17 +145,24 @@ protected function getCacheKey(string $scope, string $value): string
145145 if (!isset ($ this ->cacheKeys [$ scope ][$ value ])) {
146146 switch ($ scope ) {
147147 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 ;
149149 break ;
150150 case Constants::SCOPE_IP :
151151 case Constants::CACHE_TAG_GEO . self ::CACHE_SEP . Constants::SCOPE_IP :
152152 case Constants::CACHE_TAG_CAPTCHA . self ::CACHE_SEP . Constants::SCOPE_IP :
153153 case Constants::SCOPE_COUNTRY :
154- $ this -> cacheKeys [ $ scope ][ $ value ] = $ scope . self ::CACHE_SEP . $ value ;
154+ $ result = $ scope . self ::CACHE_SEP . $ value ;
155155 break ;
156156 default :
157157 throw new BouncerException ('Unknown scope: ' . $ scope );
158158 }
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 );
159166 }
160167
161168 return $ this ->cacheKeys [$ scope ][$ value ];
@@ -285,7 +292,8 @@ protected function addRemediationToCacheItem(
285292 ]; // erase previous decision with the same id
286293
287294 // 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 ;
289297 $ prioritizedRemediations = Remediation::sortRemediationByPriority ($ remediations );
290298
291299 $ item ->set ($ prioritizedRemediations );
@@ -341,20 +349,14 @@ protected function defferUpdateCacheConfig(array $config): void
341349 protected function formatRemediationFromDecision (?array $ decision ): array
342350 {
343351 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 ;
351353
352354 return [Constants::REMEDIATION_BYPASS , time () + $ duration , 0 ];
353355 }
354356
355357 $ duration = self ::parseDurationToSeconds ($ decision ['duration ' ]);
356358
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.
358360 if (!$ this ->streamMode ) {
359361 $ duration = min ($ this ->cacheExpirationForBadIp , $ duration );
360362 }
@@ -425,6 +427,10 @@ protected function miss(string $value, string $cacheScope): string
425427 ]);
426428 }
427429 }
430+ // In stream mode, we do not save bypass decision in cache
431+ if ($ this ->streamMode && !$ decisions ) {
432+ return Constants::REMEDIATION_BYPASS ;
433+ }
428434
429435 return $ this ->saveRemediationsForCacheKey ($ decisions , $ cacheKey );
430436 }
@@ -464,7 +470,8 @@ protected function removeDecisionFromRemediationItem(string $cacheKey, int $deci
464470 return true ;
465471 }
466472 // 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 ;
468475 $ cacheContent = Remediation::sortRemediationByPriority ($ remediations );
469476 $ item ->expiresAt (new DateTime ('@ ' . $ maxLifetime ));
470477 $ item ->set ($ cacheContent );
@@ -576,7 +583,8 @@ private function configureAdapter(): void
576583 $ this ->adapter = new RedisTagAwareAdapter ((RedisAdapter::createConnection ($ redisDsn )));
577584 } catch (Exception $ e ) {
578585 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 ());
580588 }
581589 break ;
582590
@@ -612,7 +620,7 @@ private static function parseDurationToSeconds(string $duration): int
612620 $ re = '/(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m ' ;
613621 preg_match ($ re , $ duration , $ matches );
614622 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 );
616624 }
617625 $ seconds = 0 ;
618626 if (isset ($ matches [2 ])) {
@@ -621,12 +629,14 @@ private static function parseDurationToSeconds(string $duration): int
621629 if (isset ($ matches [3 ])) {
622630 $ seconds += ((int )$ matches [3 ]) * 60 ; // minutes
623631 }
632+ $ secondsPart = 0 ;
624633 if (isset ($ matches [4 ])) {
625- $ seconds += ((int )$ matches [4 ]); // seconds
634+ $ secondsPart += ((int )$ matches [4 ]); // seconds
626635 }
627636 if ('m ' === ($ matches [5 ])) { // units in milliseconds
628- $ seconds *= 0.001 ;
637+ $ secondsPart *= 0.001 ;
629638 }
639+ $ seconds += $ secondsPart ;
630640 if ('- ' === ($ matches [1 ])) { // negative
631641 $ seconds *= -1 ;
632642 }
0 commit comments