Encode IPV6 Zone IDs in ReactorServerHttpRequest
This commit ensures that the zone id in the ReactorServerHttpRequest is properly encoded. Closes gh-30188
This commit is contained in:
parent
d6460e0d57
commit
cef9166833
|
|
@ -17,7 +17,6 @@
|
|||
package org.springframework.http.server.reactive;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
|
@ -65,52 +64,13 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest {
|
|||
public ReactorServerHttpRequest(HttpServerRequest request, NettyDataBufferFactory bufferFactory)
|
||||
throws URISyntaxException {
|
||||
|
||||
super(HttpMethod.valueOf(request.method().name()), initUri(request), "",
|
||||
super(HttpMethod.valueOf(request.method().name()), ReactorUriHelper.createUri(request), "",
|
||||
new NettyHeadersAdapter(request.requestHeaders()));
|
||||
Assert.notNull(bufferFactory, "DataBufferFactory must not be null");
|
||||
this.request = request;
|
||||
this.bufferFactory = bufferFactory;
|
||||
}
|
||||
|
||||
private static URI initUri(HttpServerRequest request) throws URISyntaxException {
|
||||
Assert.notNull(request, "HttpServerRequest must not be null");
|
||||
return new URI(resolveBaseUrl(request) + resolveRequestUri(request));
|
||||
}
|
||||
|
||||
private static String resolveBaseUrl(HttpServerRequest request) {
|
||||
String scheme = request.scheme();
|
||||
int port = request.hostPort();
|
||||
return scheme + "://" + request.hostName() + (usePort(scheme, port) ? ":" + port : "");
|
||||
}
|
||||
|
||||
private static boolean usePort(String scheme, int port) {
|
||||
return ((scheme.equals("http") || scheme.equals("ws")) && (port != 80)) ||
|
||||
((scheme.equals("https") || scheme.equals("wss")) && (port != 443));
|
||||
}
|
||||
|
||||
private static String resolveRequestUri(HttpServerRequest request) {
|
||||
String uri = request.uri();
|
||||
for (int i = 0; i < uri.length(); i++) {
|
||||
char c = uri.charAt(i);
|
||||
if (c == '/' || c == '?' || c == '#') {
|
||||
break;
|
||||
}
|
||||
if (c == ':' && (i + 2 < uri.length())) {
|
||||
if (uri.charAt(i + 1) == '/' && uri.charAt(i + 2) == '/') {
|
||||
for (int j = i + 3; j < uri.length(); j++) {
|
||||
c = uri.charAt(j);
|
||||
if (c == '/' || c == '?' || c == '#') {
|
||||
return uri.substring(j);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected MultiValueMap<String, HttpCookie> initCookies() {
|
||||
MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.server.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import reactor.netty.http.server.HttpServerRequest;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper class for creating a {@link URI} from a reactor {@link HttpServerRequest}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.0.8
|
||||
*/
|
||||
abstract class ReactorUriHelper {
|
||||
|
||||
public static URI createUri(HttpServerRequest request) throws URISyntaxException {
|
||||
Assert.notNull(request, "HttpServerRequest must not be null");
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String scheme = request.scheme();
|
||||
builder.append(scheme);
|
||||
builder.append("://");
|
||||
|
||||
appendHostName(request, builder);
|
||||
|
||||
int port = request.hostPort();
|
||||
if ((scheme.equals("http") || scheme.equals("ws")) && port != 80 ||
|
||||
(scheme.equals("https") || scheme.equals("wss")) && port != 443) {
|
||||
builder.append(':');
|
||||
builder.append(port);
|
||||
}
|
||||
|
||||
appendRequestUri(request, builder);
|
||||
|
||||
return new URI(builder.toString());
|
||||
}
|
||||
|
||||
private static void appendHostName(HttpServerRequest request, StringBuilder builder) {
|
||||
String hostName = request.hostName();
|
||||
boolean ipv6 = hostName.indexOf(':') != -1;
|
||||
boolean brackets = ipv6 && !hostName.startsWith("[") && !hostName.endsWith("]");
|
||||
if (brackets) {
|
||||
builder.append('[');
|
||||
}
|
||||
if (encoded(hostName, ipv6)) {
|
||||
builder.append(hostName);
|
||||
}
|
||||
else {
|
||||
for (int i=0; i < hostName.length(); i++) {
|
||||
char c = hostName.charAt(i);
|
||||
if (isAllowedInHost(c, ipv6)) {
|
||||
builder.append(c);
|
||||
}
|
||||
else {
|
||||
builder.append('%');
|
||||
char hex1 = Character.toUpperCase(Character.forDigit((c >> 4) & 0xF, 16));
|
||||
char hex2 = Character.toUpperCase(Character.forDigit(c & 0xF, 16));
|
||||
builder.append(hex1);
|
||||
builder.append(hex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (brackets) {
|
||||
builder.append(']');
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean encoded(String hostName, boolean ipv6) {
|
||||
int length = hostName.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = hostName.charAt(i);
|
||||
if (c == '%') {
|
||||
if ((i + 2) < length) {
|
||||
char hex1 = hostName.charAt(i + 1);
|
||||
char hex2 = hostName.charAt(i + 2);
|
||||
int u = Character.digit(hex1, 16);
|
||||
int l = Character.digit(hex2, 16);
|
||||
if (u == -1 || l == -1) {
|
||||
return false;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!isAllowedInHost(c, ipv6)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isAllowedInHost(char c, boolean ipv6) {
|
||||
return (c >= 'a' && c <= 'z') || // alpha
|
||||
(c >= 'A' && c <= 'Z') || // alpha
|
||||
(c >= '0' && c <= '9') || // digit
|
||||
'-' == c || '.' == c || '_' == c || '~' == c || // unreserved
|
||||
'!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || // sub-delims
|
||||
'*' == c || '+' == c || ',' == c || ';' == c || '=' == c ||
|
||||
(ipv6 && ('[' == c || ']' == c || ':' == c)); // ipv6
|
||||
}
|
||||
|
||||
private static void appendRequestUri(HttpServerRequest request, StringBuilder builder) {
|
||||
String uri = request.uri();
|
||||
int length = uri.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = uri.charAt(i);
|
||||
if (c == '/' || c == '?' || c == '#') {
|
||||
break;
|
||||
}
|
||||
if (c == ':' && (i + 2 < length)) {
|
||||
if (uri.charAt(i + 1) == '/' && uri.charAt(i + 2) == '/') {
|
||||
for (int j = i + 3; j < length; j++) {
|
||||
c = uri.charAt(j);
|
||||
if (c == '/' || c == '?' || c == '#') {
|
||||
builder.append(uri, j, length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.append(uri);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2002-2023 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.server.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.netty.http.server.HttpServerRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class ReactorUriHelperTests {
|
||||
|
||||
@Test
|
||||
public void hostnameWithZoneId() throws URISyntaxException {
|
||||
HttpServerRequest nettyRequest = mock();
|
||||
|
||||
given(nettyRequest.scheme()).willReturn("http");
|
||||
given(nettyRequest.hostName()).willReturn("fe80::a%en1");
|
||||
given(nettyRequest.hostPort()).willReturn(80);
|
||||
given(nettyRequest.uri()).willReturn("/");
|
||||
|
||||
URI uri = ReactorUriHelper.createUri(nettyRequest);
|
||||
assertThat(uri).hasScheme("http")
|
||||
.hasHost("[fe80::a%25en1]")
|
||||
.hasPort(-1)
|
||||
.hasPath("/")
|
||||
.hasToString("http://[fe80::a%25en1]/");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue