Skip to content

Commit 367a620

Browse files
committed
Merge branch '7.0.x'
2 parents 1b26f5d + d72da90 commit 367a620

12 files changed

Lines changed: 120 additions & 61 deletions

File tree

framework-platform/framework-platform.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ dependencies {
138138
api("org.seleniumhq.selenium:selenium-java:4.41.0")
139139
api("org.skyscreamer:jsonassert:1.5.3")
140140
api("org.testng:testng:7.12.0")
141-
api("org.webjars:underscorejs:1.8.3")
142141
api("org.webjars:webjars-locator-lite:1.1.0")
143142
api("org.xmlunit:xmlunit-assertj:2.10.4")
144143
api("org.xmlunit:xmlunit-matchers:2.10.4")

spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ private static MimeType parseMimeTypeInternal(String mimeType) {
238238
break;
239239
}
240240
}
241-
else if (ch == '"') {
241+
else if (ch == '"' && mimeType.charAt(nextIndex - 1) != '\\') {
242242
quoted = !quoted;
243243
}
244244
nextIndex++;

spring-core/src/test/java/org/springframework/util/MimeTypeTests.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ void parseQuotedCharset() {
9898
}
9999

100100
@Test
101-
void parseQuotedSeparator() {
101+
void parseQuotedParameterValue() {
102102
String s = "application/xop+xml;charset=utf-8;type=\"application/soap+xml;action=\\\"https://x.y.z\\\"\"";
103103
MimeType mimeType = MimeType.valueOf(s);
104104
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
@@ -107,6 +107,15 @@ void parseQuotedSeparator() {
107107
assertThat(mimeType.getParameter("type")).isEqualTo("\"application/soap+xml;action=\\\"https://x.y.z\\\"\"");
108108
}
109109

110+
@Test
111+
void parseParameterWithQuotedPair() {
112+
String s = "text/plain;twelve=\"1\\\"2\"";
113+
MimeType mimeType = MimeType.valueOf(s);
114+
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("text");
115+
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("plain");
116+
assertThat(mimeType.getParameter("twelve")).isEqualTo("\"1\\\"2\"");
117+
}
118+
110119
@Test
111120
void withConversionService() {
112121
ConversionService conversionService = new DefaultConversionService();

spring-web/src/main/java/org/springframework/web/server/WebSession.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,6 @@ default <T> T getAttributeOrDefault(String name, T defaultValue) {
100100
*/
101101
boolean isStarted();
102102

103-
/**
104-
* Generate a new id for the session and update the underlying session
105-
* storage to reflect the new id. After a successful call {@link #getId()}
106-
* reflects the new session id.
107-
* @return completion notification (success or error)
108-
*/
109-
Mono<Void> changeSessionId();
110-
111-
/**
112-
* Invalidate the current session and clear session storage.
113-
* @return completion notification (success or error)
114-
*/
115-
Mono<Void> invalidate();
116-
117103
/**
118104
* Save the session through the {@code WebSessionStore} as follows:
119105
* <ul>
@@ -131,6 +117,20 @@ default <T> T getAttributeOrDefault(String name, T defaultValue) {
131117
*/
132118
Mono<Void> save();
133119

120+
/**
121+
* Generate a new id for the session and update the underlying session
122+
* storage to reflect the new id. After a successful call {@link #getId()}
123+
* reflects the new session id.
124+
* @return completion notification (success or error)
125+
*/
126+
Mono<Void> changeSessionId();
127+
128+
/**
129+
* Invalidate the current session and clear session storage.
130+
* @return completion notification (success or error)
131+
*/
132+
Mono<Void> invalidate();
133+
134134
/**
135135
* Return {@code true} if the session expired after {@link #getMaxIdleTime()
136136
* maxIdleTime} elapsed.

spring-web/src/main/java/org/springframework/web/server/session/InMemoryWebSessionStore.java

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ private class InMemoryWebSession implements WebSession {
207207

208208
private final AtomicReference<State> state = new AtomicReference<>(State.NEW);
209209

210+
private final Lock lock = new ReentrantLock();
210211

211212
public InMemoryWebSession(Instant creationTime, Duration maxIdleTime) {
212213
this.creationTime = creationTime;
@@ -256,29 +257,6 @@ public boolean isStarted() {
256257
return this.state.get().equals(State.STARTED) || !getAttributes().isEmpty();
257258
}
258259

259-
@Override
260-
public Mono<Void> changeSessionId() {
261-
return Mono.<Void>defer(() -> {
262-
String currentId = this.id.get();
263-
InMemoryWebSessionStore.this.sessions.remove(currentId);
264-
String newId = String.valueOf(idGenerator.generateId());
265-
this.id.set(newId);
266-
InMemoryWebSessionStore.this.sessions.put(this.id.get(), this);
267-
return Mono.empty();
268-
})
269-
.subscribeOn(Schedulers.boundedElastic())
270-
.publishOn(Schedulers.parallel())
271-
.then();
272-
}
273-
274-
@Override
275-
public Mono<Void> invalidate() {
276-
this.state.set(State.EXPIRED);
277-
getAttributes().clear();
278-
InMemoryWebSessionStore.this.sessions.remove(this.id.get());
279-
return Mono.empty();
280-
}
281-
282260
@Override
283261
@SuppressWarnings("NullAway") // Dataflow analysis limitation
284262
public Mono<Void> save() {
@@ -292,11 +270,19 @@ public Mono<Void> save() {
292270

293271
if (isStarted()) {
294272
// Save
295-
InMemoryWebSessionStore.this.sessions.put(this.id.get(), this);
273+
if (InMemoryWebSessionStore.this.sessions.get(getId()) == null) {
274+
this.lock.lock();
275+
try {
276+
InMemoryWebSessionStore.this.sessions.putIfAbsent(getId(), this);
277+
}
278+
finally {
279+
this.lock.unlock();
280+
}
281+
}
296282

297283
// Unless it was invalidated
298284
if (this.state.get().equals(State.EXPIRED)) {
299-
InMemoryWebSessionStore.this.sessions.remove(this.id.get());
285+
InMemoryWebSessionStore.this.sessions.remove(getId());
300286
return Mono.error(new IllegalStateException("Session was invalidated"));
301287
}
302288
}
@@ -307,12 +293,41 @@ public Mono<Void> save() {
307293
private void checkMaxSessionsLimit() {
308294
if (sessions.size() >= maxSessions) {
309295
expiredSessionChecker.removeExpiredSessions(clock.instant());
310-
if (sessions.size() >= maxSessions && !sessions.containsKey(this.id.get())) {
296+
if (sessions.size() >= maxSessions && !sessions.containsKey(getId())) {
311297
throw new IllegalStateException("Max sessions limit reached: " + sessions.size());
312298
}
313299
}
314300
}
315301

302+
@Override
303+
public Mono<Void> changeSessionId() {
304+
return Mono.<Void>defer(() -> {
305+
this.lock.lock();
306+
try {
307+
String oldId = getId();
308+
String newId = String.valueOf(idGenerator.generateId());
309+
InMemoryWebSessionStore.this.sessions.remove(oldId);
310+
InMemoryWebSessionStore.this.sessions.put(newId, this);
311+
this.id.set(newId);
312+
}
313+
finally {
314+
this.lock.unlock();
315+
}
316+
return Mono.empty();
317+
})
318+
.subscribeOn(Schedulers.boundedElastic())
319+
.publishOn(Schedulers.parallel())
320+
.then();
321+
}
322+
323+
@Override
324+
public Mono<Void> invalidate() {
325+
this.state.set(State.EXPIRED);
326+
getAttributes().clear();
327+
InMemoryWebSessionStore.this.sessions.remove(getId());
328+
return Mono.empty();
329+
}
330+
316331
@Override
317332
public boolean isExpired() {
318333
return isExpired(clock.instant());

spring-webflux/spring-webflux.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ dependencies {
6161
testRuntimeOnly("org.glassfish:jakarta.el")
6262
testRuntimeOnly("org.jruby:jruby")
6363
testRuntimeOnly("org.python:jython-standalone")
64-
testRuntimeOnly("org.webjars:underscorejs")
64+
testRuntimeOnly("org.webjars:momentjs:2.29.4")
6565
}
6666

6767
test {

spring-webflux/src/main/java/org/springframework/web/reactive/resource/LiteWebJarsResourceResolver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ protected Mono<String> resolveUrlPathInternal(String resourceUrlPath,
9090
.switchIfEmpty(Mono.defer(() -> {
9191
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
9292
if (webJarResourcePath != null) {
93-
return chain.resolveUrlPath(webJarResourcePath, locations);
93+
Mono<String> fallback = (webJarResourcePath.endsWith("/")) ? Mono.just(webJarResourcePath) : Mono.empty();
94+
return chain.resolveUrlPath(webJarResourcePath, locations).switchIfEmpty(fallback);
9495
}
9596
else {
9697
return Mono.empty();

spring-webflux/src/test/java/org/springframework/web/reactive/resource/LiteWebJarsResourceResolverTests.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.springframework.web.testfixture.server.MockServerWebExchange;
3030

3131
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.mockito.ArgumentMatchers.anyString;
33+
import static org.mockito.ArgumentMatchers.eq;
3234
import static org.mockito.BDDMockito.given;
3335
import static org.mockito.Mockito.mock;
3436
import static org.mockito.Mockito.never;
@@ -80,8 +82,8 @@ void resolveUrlExistingNotInJarFile() {
8082

8183
@Test
8284
void resolveUrlWebJarResource() {
83-
String file = "underscorejs/underscore.js";
84-
String expected = "underscorejs/1.8.3/underscore.js";
85+
String file = "momentjs/momentjs.js";
86+
String expected = "momentjs/2.29.4/momentjs.js";
8587
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
8688
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.just(expected));
8789

@@ -92,10 +94,24 @@ void resolveUrlWebJarResource() {
9294
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
9395
}
9496

97+
@Test
98+
void resolveUrlWebJarDirectory() {
99+
String folder = "momentjs/locale/";
100+
String expected = "momentjs/2.29.4/locale/";
101+
given(this.chain.resolveUrlPath(folder, this.locations)).willReturn(Mono.empty());
102+
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(Mono.empty());
103+
104+
String actual = this.resolver.resolveUrlPath(folder, this.locations, this.chain).block(TIMEOUT);
105+
106+
assertThat(actual).isEqualTo(expected);
107+
verify(this.chain, times(1)).resolveUrlPath(folder, this.locations);
108+
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
109+
}
110+
95111
@Test
96112
void resolveUrlWebJarResourceNotFound() {
97-
String file = "something/something.js";
98-
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(Mono.empty());
113+
String file = "momentjs/locale/unknown.js";
114+
given(this.chain.resolveUrlPath(anyString(), eq(this.locations))).willReturn(Mono.empty());
99115

100116
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain).block(TIMEOUT);
101117

@@ -134,11 +150,11 @@ void resolveResourceNotFound() {
134150

135151
@Test
136152
void resolveResourceWebJar() {
137-
String file = "underscorejs/underscore.js";
153+
String file = "momentjs/momentjs.js";
138154
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(Mono.empty());
139155

140156
Resource expected = mock();
141-
String expectedPath = "underscorejs/1.8.3/underscore.js";
157+
String expectedPath = "momentjs/2.29.4/momentjs.js";
142158
given(this.chain.resolveResource(this.exchange, expectedPath, this.locations))
143159
.willReturn(Mono.just(expected));
144160

spring-webmvc/spring-webmvc.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,5 @@ dependencies {
8080
testRuntimeOnly("org.glassfish:jakarta.el")
8181
testRuntimeOnly("org.jruby:jruby")
8282
testRuntimeOnly("org.python:jython-standalone")
83-
testRuntimeOnly("org.webjars:underscorejs")
83+
testRuntimeOnly("org.webjars:momentjs:2.29.4")
8484
}

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/LiteWebJarsResourceResolver.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ public LiteWebJarsResourceResolver(WebJarVersionLocator webJarVersionLocator) {
8888
if (path == null) {
8989
String webJarResourcePath = findWebJarResourcePath(resourceUrlPath);
9090
if (webJarResourcePath != null) {
91-
return chain.resolveUrlPath(webJarResourcePath, locations);
91+
path = chain.resolveUrlPath(webJarResourcePath, locations);
92+
if (path == null && webJarResourcePath.endsWith("/")) {
93+
path = webJarResourcePath;
94+
}
9295
}
9396
}
9497
return path;

0 commit comments

Comments
 (0)