Browse Source

Merge branch '5.1.x'

pull/23557/head
Rossen Stoyanchev 5 years ago
parent
commit
117119a88d
  1. 2
      build.gradle
  2. 42
      spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java
  3. 15
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilter.java
  4. 10
      spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProviderExposingInterceptor.java
  5. 25
      spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilterTests.java
  6. 20
      spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java

2
build.gradle

@ -24,7 +24,7 @@ ext { @@ -24,7 +24,7 @@ ext {
groovyVersion = "2.5.7"
hsqldbVersion = "2.5.0"
jackson2Version = "2.9.9"
jettyVersion = "9.4.19.v20190610"
jettyVersion = "9.4.20.v20190813"
junit5Version = "5.5.1"
kotlinVersion = "1.3.50"
log4jVersion = "2.12.0"

42
spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerReadPublisher.java

@ -224,6 +224,23 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> { @@ -224,6 +224,23 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
}
}
private void handleCompletionOrErrorBeforeDemand() {
State state = this.state.get();
if (!state.equals(State.UNSUBSCRIBED) && !state.equals(State.SUBSCRIBING)) {
if (this.completionBeforeDemand) {
rsReadLogger.trace(getLogPrefix() + "Completed before demand");
this.state.get().onAllDataRead(this);
}
Throwable ex = this.errorBeforeDemand;
if (ex != null) {
if (rsReadLogger.isTraceEnabled()) {
rsReadLogger.trace(getLogPrefix() + "Completed with error before demand: " + ex);
}
this.state.get().onError(this, ex);
}
}
}
private Subscription createSubscription() {
return new ReadSubscription();
}
@ -283,7 +300,7 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> { @@ -283,7 +300,7 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
publisher.subscriber = subscriber;
subscriber.onSubscribe(subscription);
publisher.changeState(SUBSCRIBING, NO_DEMAND);
handleCompletionOrErrorBeforeDemand(publisher);
publisher.handleCompletionOrErrorBeforeDemand();
}
else {
throw new IllegalStateException("Failed to transition to SUBSCRIBING, " +
@ -294,30 +311,13 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> { @@ -294,30 +311,13 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
@Override
<T> void onAllDataRead(AbstractListenerReadPublisher<T> publisher) {
publisher.completionBeforeDemand = true;
handleCompletionOrErrorBeforeDemand(publisher);
publisher.handleCompletionOrErrorBeforeDemand();
}
@Override
<T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable ex) {
publisher.errorBeforeDemand = ex;
handleCompletionOrErrorBeforeDemand(publisher);
}
private <T> void handleCompletionOrErrorBeforeDemand(AbstractListenerReadPublisher<T> publisher) {
if (publisher.state.get().equals(NO_DEMAND)) {
if (publisher.completionBeforeDemand) {
rsReadLogger.trace(publisher.getLogPrefix() + "Completed before demand");
publisher.state.get().onAllDataRead(publisher);
}
Throwable ex = publisher.errorBeforeDemand;
if (ex != null) {
if (rsReadLogger.isTraceEnabled()) {
String prefix = publisher.getLogPrefix();
rsReadLogger.trace(prefix + "Completed with error before demand: " + ex);
}
publisher.state.get().onError(publisher, ex);
}
}
publisher.handleCompletionOrErrorBeforeDemand();
}
},
@ -337,11 +337,13 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> { @@ -337,11 +337,13 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
@Override
<T> void onAllDataRead(AbstractListenerReadPublisher<T> publisher) {
publisher.completionBeforeDemand = true;
publisher.handleCompletionOrErrorBeforeDemand();
}
@Override
<T> void onError(AbstractListenerReadPublisher<T> publisher, Throwable ex) {
publisher.errorBeforeDemand = ex;
publisher.handleCompletionOrErrorBeforeDemand();
}
},

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

@ -96,10 +96,7 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean { @@ -96,10 +96,7 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean {
String lookupPath = pathHelper.getLookupPathForRequest(this);
this.indexLookupPath = requestUri.lastIndexOf(lookupPath);
if (this.indexLookupPath == -1) {
throw new IllegalStateException(
"Failed to find lookupPath '" + lookupPath + "' within requestUri '" + requestUri + "'. " +
"Does the path have invalid encoded characters for characterEncoding '" +
getRequest().getCharacterEncoding() + "'?");
throw new LookupPathIndexException(lookupPath, requestUri);
}
this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath);
if ("/".equals(lookupPath) && !"/".equals(requestUri)) {
@ -164,4 +161,14 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean { @@ -164,4 +161,14 @@ public class ResourceUrlEncodingFilter extends GenericFilterBean {
}
}
@SuppressWarnings("serial")
static class LookupPathIndexException extends IllegalArgumentException {
LookupPathIndexException(String lookupPath, String requestUri) {
super("Failed to find lookupPath '" + lookupPath + "' within requestUri '" + requestUri + "'. " +
"This could be because the path has invalid encoded characters or isn't normalized.");
}
}
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest; @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
@ -48,7 +49,12 @@ public class ResourceUrlProviderExposingInterceptor extends HandlerInterceptorAd @@ -48,7 +49,12 @@ public class ResourceUrlProviderExposingInterceptor extends HandlerInterceptorAd
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
request.setAttribute(RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider);
try {
request.setAttribute(RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider);
}
catch (ResourceUrlEncodingFilter.LookupPathIndexException ex) {
throw new ServletRequestBindingException(ex.getMessage(), ex);
}
return true;
}

25
spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceUrlEncodingFilterTests.java

@ -19,7 +19,9 @@ import java.io.IOException; @@ -19,7 +19,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
@ -28,8 +30,10 @@ import org.junit.jupiter.api.Test; @@ -28,8 +30,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.web.bind.ServletRequestBindingException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Unit tests for {@code ResourceUrlEncodingFilter}.
@ -155,14 +159,31 @@ public class ResourceUrlEncodingFilterTests { @@ -155,14 +159,31 @@ public class ResourceUrlEncodingFilterTests {
"/resources/bar-11e16cf79faee7ac698c805cf28248d2.css?foo=bar&url=https://example.org#something");
}
@Test // gh-23508
public void invalidLookupPath() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/a/b/../logo.png");
request.setServletPath("/a/logo.png");
this.filter.doFilter(request, new MockHttpServletResponse(), (req, res) -> {
ResourceUrlProviderExposingInterceptor interceptor =
new ResourceUrlProviderExposingInterceptor(this.urlProvider);
assertThatThrownBy(() -> interceptor.preHandle((HttpServletRequest) req, (HttpServletResponse) res, null))
.isInstanceOf(ServletRequestBindingException.class);
});
}
private void testEncodeUrl(MockHttpServletRequest request, String url, String expected)
throws ServletException, IOException {
this.filter.doFilter(request, new MockHttpServletResponse(), (req, res) -> {
FilterChain chain = (req, res) -> {
req.setAttribute(ResourceUrlProviderExposingInterceptor.RESOURCE_URL_PROVIDER_ATTR, this.urlProvider);
String result = ((HttpServletResponse) res).encodeURL(url);
assertThat(result).isEqualTo(expected);
});
};
this.filter.doFilter(request, new MockHttpServletResponse(), chain);
}
}

20
spring-websocket/src/main/java/org/springframework/web/socket/server/jetty/JettyRequestUpgradeStrategy.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package org.springframework.web.socket.server.jetty;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
@ -28,6 +29,7 @@ import javax.servlet.http.HttpServletResponse; @@ -28,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
import org.eclipse.jetty.websocket.server.HandshakeRFC6455;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
@ -38,7 +40,9 @@ import org.springframework.http.server.ServerHttpResponse; @@ -38,7 +40,9 @@ import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.socket.WebSocketExtension;
import org.springframework.web.socket.WebSocketHandler;
@ -167,7 +171,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Serv @@ -167,7 +171,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Serv
}
private List<WebSocketExtension> buildWebSocketExtensions() {
Set<String> names = this.factory.getExtensionFactory().getExtensionNames();
Set<String> names = getExtensionNames();
List<WebSocketExtension> result = new ArrayList<>(names.size());
for (String name : names) {
result.add(new WebSocketExtension(name));
@ -175,6 +179,18 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Serv @@ -175,6 +179,18 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Serv
return result;
}
@SuppressWarnings({"unchecked"})
private Set<String> getExtensionNames() {
try {
return this.factory.getExtensionFactory().getExtensionNames();
}
catch (IncompatibleClassChangeError ex) {
// 9.4.20.v20190813: ExtensionFactory (abstract class -> interface)
Method method = ClassUtils.getMethod(ExtensionFactory.class, "getExtensionNames");
return (Set<String>) ReflectionUtils.invokeMethod(method, this.factory.getExtensionFactory());
}
}
@Override
public void upgrade(ServerHttpRequest request, ServerHttpResponse response,
String selectedProtocol, List<WebSocketExtension> selectedExtensions, Principal user,

Loading…
Cancel
Save