spring-framework-0001 (UNDF-734): CorsConfiguration.checkHeaders() O(R×A) - requestHeaders list (R) × allowedHeaders ArrayList (A) nested loop - equalsIgnoreCase() scan per header — 14x measured at R=20, A=50 - Fix: build lowercase LinkedHashSet once O(A), lookup O(1) per header spring-framework-0002 (UNDF-735): AcceptHeaderLocaleResolver O(R×S) - Accept-Language locales (R) × supportedLocales ArrayList (S) - per-request linear scan: 9x at R=15, S=20 - Same defect in AcceptHeaderLocaleContextResolver (reactive) - Fix: LinkedHashSet for O(1) full-locale match spring-framework-0003 (UNDF-736): EncodedResourceResolver.contentCodings O(A×C) - acceptedCodings (A) × contentCodings ArrayList (C) per static resource request - 8x at A=10, C=8 - Fix: LinkedHashSet for O(1) contains per accepted encoding - Both spring-webmvc and spring-webflux variants 3/3 unit tests PASS
68 lines
2.6 KiB
Diff
68 lines
2.6 KiB
Diff
# UNDF: UNDF-2026-000000734
|
||
--- a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
|
||
+++ b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
|
||
@@ -19,6 +19,7 @@ import java.time.Duration;
|
||
import java.util.ArrayList;
|
||
import java.util.Collections;
|
||
import java.util.List;
|
||
+import java.util.LinkedHashSet;
|
||
import java.util.Set;
|
||
import java.util.function.Consumer;
|
||
import java.util.regex.Matcher;
|
||
@@ -77,7 +78,7 @@ public class CorsConfiguration {
|
||
|
||
private @Nullable List<String> allowedHeaders;
|
||
|
||
- // allowedHeaders is stored as ArrayList — checkHeaders() does O(R×A)
|
||
- // linear scan per request header against every allowed header entry.
|
||
+ // FIX: allowedHeaders now uses LinkedHashSet for O(1) membership test.
|
||
+ // checkHeaders() becomes O(R) instead of O(R×A).
|
||
|
||
@@ -363,7 +364,7 @@ public class CorsConfiguration {
|
||
*/
|
||
public void setAllowedHeaders(@Nullable List<String> allowedHeaders) {
|
||
- this.allowedHeaders = (allowedHeaders != null ? new ArrayList<>(allowedHeaders) : null);
|
||
+ this.allowedHeaders = (allowedHeaders != null ? new ArrayList<>(new LinkedHashSet<>(allowedHeaders)) : null);
|
||
}
|
||
@@ -380,7 +381,7 @@ public class CorsConfiguration {
|
||
*/
|
||
public void addAllowedHeader(String allowedHeader) {
|
||
if (this.allowedHeaders == null) {
|
||
- this.allowedHeaders = new ArrayList<>(4);
|
||
+ this.allowedHeaders = new ArrayList<>(4); // kept as ArrayList for ordered getter
|
||
}
|
||
else if (this.allowedHeaders == DEFAULT_PERMIT_ALL) {
|
||
setAllowedHeaders(DEFAULT_PERMIT_ALL);
|
||
@@ -724,8 +725,14 @@ public class CorsConfiguration {
|
||
}
|
||
|
||
boolean allowAnyHeader = this.allowedHeaders.contains(ALL);
|
||
+ // Build a case-insensitive lookup set for O(1) per-header check
|
||
+ // instead of O(A) linear scan through the allowedHeaders list.
|
||
+ Set<String> allowedSet = null;
|
||
+ if (!allowAnyHeader) {
|
||
+ allowedSet = new LinkedHashSet<>(this.allowedHeaders.size() * 2);
|
||
+ for (String h : this.allowedHeaders) {
|
||
+ allowedSet.add(h.toLowerCase(java.util.Locale.ROOT));
|
||
+ }
|
||
+ }
|
||
int maxResultSize = (allowAnyHeader ? requestHeaders.size() :
|
||
Math.min(requestHeaders.size(), this.allowedHeaders.size()));
|
||
List<String> result = new ArrayList<>(maxResultSize);
|
||
@@ -735,12 +742,9 @@ public class CorsConfiguration {
|
||
if (allowAnyHeader) {
|
||
result.add(requestHeader);
|
||
}
|
||
else {
|
||
- for (String allowedHeader : this.allowedHeaders) {
|
||
- if (requestHeader.equalsIgnoreCase(allowedHeader)) {
|
||
- result.add(requestHeader);
|
||
- break;
|
||
- }
|
||
- }
|
||
+ if (allowedSet.contains(requestHeader.toLowerCase(java.util.Locale.ROOT))) {
|
||
+ result.add(requestHeader);
|
||
+ }
|
||
}
|
||
}
|
||
}
|