Skip to content

Commit 9a52c81

Browse files
committed
UriComponentsBuilder allows more efficient (and less restrictive) CORS origin comparison
Also introduces a specifically narrowed cloneBuilder() method for UriComponentsBuilders. Issue: SPR-14080
1 parent abe7345 commit 9a52c81

File tree

4 files changed

+122
-88
lines changed

4 files changed

+122
-88
lines changed

spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

Lines changed: 99 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import java.util.regex.Matcher;
2525
import java.util.regex.Pattern;
2626

27+
import org.springframework.http.HttpHeaders;
2728
import org.springframework.http.HttpRequest;
2829
import org.springframework.util.Assert;
2930
import org.springframework.util.LinkedMultiValueMap;
@@ -130,7 +131,7 @@ protected UriComponentsBuilder(UriComponentsBuilder other) {
130131
this.userInfo = other.userInfo;
131132
this.host = other.host;
132133
this.port = other.port;
133-
this.pathBuilder = (CompositePathComponentBuilder) other.pathBuilder.clone();
134+
this.pathBuilder = other.pathBuilder.cloneBuilder();
134135
this.queryParams.putAll(other.queryParams);
135136
this.fragment = other.fragment;
136137
}
@@ -271,72 +272,17 @@ public static UriComponentsBuilder fromHttpUrl(String httpUrl) {
271272
/**
272273
* Create a new {@code UriComponents} object from the URI associated with
273274
* the given HttpRequest while also overlaying with values from the headers
274-
* "Forwarded" (<a href="http://tools.ietf.org/html/rfc7239">RFC 7239</a>, or
275-
* "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if "Forwarded" is
276-
* not found.
275+
* "Forwarded" (<a href="http://tools.ietf.org/html/rfc7239">RFC 7239</a>,
276+
* or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if
277+
* "Forwarded" is not found.
277278
* @param request the source request
278279
* @return the URI components of the URI
279280
* @since 4.1.5
280281
*/
281282
public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
282-
URI uri = request.getURI();
283-
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(uri);
284-
285-
String scheme = uri.getScheme();
286-
String host = uri.getHost();
287-
int port = uri.getPort();
288-
289-
String forwardedHeader = request.getHeaders().getFirst("Forwarded");
290-
if (StringUtils.hasText(forwardedHeader)) {
291-
String forwardedToUse = StringUtils.commaDelimitedListToStringArray(forwardedHeader)[0];
292-
Matcher m = FORWARDED_HOST_PATTERN.matcher(forwardedToUse);
293-
if (m.find()) {
294-
host = m.group(1).trim();
295-
}
296-
m = FORWARDED_PROTO_PATTERN.matcher(forwardedToUse);
297-
if (m.find()) {
298-
scheme = m.group(1).trim();
299-
}
300-
}
301-
else {
302-
String hostHeader = request.getHeaders().getFirst("X-Forwarded-Host");
303-
if (StringUtils.hasText(hostHeader)) {
304-
String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
305-
String hostToUse = hosts[0];
306-
if (hostToUse.contains(":")) {
307-
String[] hostAndPort = StringUtils.split(hostToUse, ":");
308-
host = hostAndPort[0];
309-
port = Integer.parseInt(hostAndPort[1]);
310-
}
311-
else {
312-
host = hostToUse;
313-
port = -1;
314-
}
315-
}
316-
317-
String portHeader = request.getHeaders().getFirst("X-Forwarded-Port");
318-
if (StringUtils.hasText(portHeader)) {
319-
String[] ports = StringUtils.commaDelimitedListToStringArray(portHeader);
320-
port = Integer.parseInt(ports[0]);
321-
}
322-
323-
String protocolHeader = request.getHeaders().getFirst("X-Forwarded-Proto");
324-
if (StringUtils.hasText(protocolHeader)) {
325-
String[] protocols = StringUtils.commaDelimitedListToStringArray(protocolHeader);
326-
scheme = protocols[0];
327-
}
328-
}
329-
330-
builder.scheme(scheme);
331-
builder.host(host);
332-
builder.port(null);
333-
if (scheme.equals("http") && port != 80 || scheme.equals("https") && port != 443) {
334-
builder.port(port);
335-
}
336-
return builder;
283+
return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders());
337284
}
338285

339-
340286
/**
341287
* Create an instance by parsing the "Origin" header of an HTTP request.
342288
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
@@ -463,18 +409,6 @@ public UriComponentsBuilder uri(URI uri) {
463409
return this;
464410
}
465411

466-
private void resetHierarchicalComponents() {
467-
this.userInfo = null;
468-
this.host = null;
469-
this.port = null;
470-
this.pathBuilder = new CompositePathComponentBuilder();
471-
this.queryParams.clear();
472-
}
473-
474-
private void resetSchemeSpecificPart() {
475-
this.ssp = null;
476-
}
477-
478412
/**
479413
* Set the URI scheme. The given scheme may contain URI template variables,
480414
* and may also be {@code null} to clear the scheme of this builder.
@@ -724,17 +658,103 @@ public UriComponentsBuilder fragment(String fragment) {
724658
return this;
725659
}
726660

661+
/**
662+
* Adapt this builder's scheme+host+port from the given headers, specifically
663+
* "Forwarded" (<a href="http://tools.ietf.org/html/rfc7239">RFC 7239</a>,
664+
* or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if
665+
* "Forwarded" is not found.
666+
* @param headers the HTTP headers to consider
667+
* @return this UriComponentsBuilder
668+
* @since 4.3
669+
*/
670+
UriComponentsBuilder adaptFromForwardedHeaders(HttpHeaders headers) {
671+
String forwardedHeader = headers.getFirst("Forwarded");
672+
if (StringUtils.hasText(forwardedHeader)) {
673+
String forwardedToUse = StringUtils.commaDelimitedListToStringArray(forwardedHeader)[0];
674+
Matcher matcher = FORWARDED_HOST_PATTERN.matcher(forwardedToUse);
675+
if (matcher.find()) {
676+
host(matcher.group(1).trim());
677+
}
678+
matcher = FORWARDED_PROTO_PATTERN.matcher(forwardedToUse);
679+
if (matcher.find()) {
680+
scheme(matcher.group(1).trim());
681+
}
682+
}
683+
else {
684+
String hostHeader = headers.getFirst("X-Forwarded-Host");
685+
if (StringUtils.hasText(hostHeader)) {
686+
String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
687+
String hostToUse = hosts[0];
688+
if (hostToUse.contains(":")) {
689+
String[] hostAndPort = StringUtils.split(hostToUse, ":");
690+
host(hostAndPort[0]);
691+
port(Integer.parseInt(hostAndPort[1]));
692+
}
693+
else {
694+
host(hostToUse);
695+
port(null);
696+
}
697+
}
698+
699+
String portHeader = headers.getFirst("X-Forwarded-Port");
700+
if (StringUtils.hasText(portHeader)) {
701+
String[] ports = StringUtils.commaDelimitedListToStringArray(portHeader);
702+
port(Integer.parseInt(ports[0]));
703+
}
704+
705+
String protocolHeader = headers.getFirst("X-Forwarded-Proto");
706+
if (StringUtils.hasText(protocolHeader)) {
707+
String[] protocols = StringUtils.commaDelimitedListToStringArray(protocolHeader);
708+
scheme(protocols[0]);
709+
}
710+
}
711+
712+
if ((this.scheme.equals("http") && "80".equals(this.port)) ||
713+
(this.scheme.equals("https") && "443".equals(this.port))) {
714+
this.port = null;
715+
}
716+
717+
return this;
718+
}
719+
720+
private void resetHierarchicalComponents() {
721+
this.userInfo = null;
722+
this.host = null;
723+
this.port = null;
724+
this.pathBuilder = new CompositePathComponentBuilder();
725+
this.queryParams.clear();
726+
}
727+
728+
private void resetSchemeSpecificPart() {
729+
this.ssp = null;
730+
}
731+
732+
733+
/**
734+
* Public declaration of Object's {@code clone()} method.
735+
* Delegates to {@link #cloneBuilder()}.
736+
* @see Object#clone()
737+
*/
727738
@Override
728739
public Object clone() {
740+
return cloneBuilder();
741+
}
742+
743+
/**
744+
* Clone this {@code UriComponentsBuilder}.
745+
* @return the cloned {@code UriComponentsBuilder} object
746+
* @since 4.3
747+
*/
748+
public UriComponentsBuilder cloneBuilder() {
729749
return new UriComponentsBuilder(this);
730750
}
731751

732752

733-
private interface PathComponentBuilder extends Cloneable {
753+
private interface PathComponentBuilder {
734754

735755
PathComponent build();
736756

737-
Object clone();
757+
PathComponentBuilder cloneBuilder();
738758
}
739759

740760

@@ -810,10 +830,10 @@ public PathComponent build() {
810830
}
811831

812832
@Override
813-
public Object clone() {
833+
public CompositePathComponentBuilder cloneBuilder() {
814834
CompositePathComponentBuilder compositeBuilder = new CompositePathComponentBuilder();
815835
for (PathComponentBuilder builder : this.builders) {
816-
compositeBuilder.builders.add((PathComponentBuilder) builder.clone());
836+
compositeBuilder.builders.add(builder.cloneBuilder());
817837
}
818838
return compositeBuilder;
819839
}
@@ -852,7 +872,7 @@ public void removeTrailingSlash() {
852872
}
853873

854874
@Override
855-
public Object clone() {
875+
public FullPathComponentBuilder cloneBuilder() {
856876
FullPathComponentBuilder builder = new FullPathComponentBuilder();
857877
builder.append(this.path.toString());
858878
return builder;
@@ -879,7 +899,7 @@ public PathComponent build() {
879899
}
880900

881901
@Override
882-
public Object clone() {
902+
public PathSegmentComponentBuilder cloneBuilder() {
883903
PathSegmentComponentBuilder builder = new PathSegmentComponentBuilder();
884904
builder.pathSegments.addAll(this.pathSegments);
885905
return builder;

spring-web/src/main/java/org/springframework/web/util/WebUtils.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import javax.servlet.http.HttpSession;
3535

3636
import org.springframework.http.HttpRequest;
37+
import org.springframework.http.server.ServletServerHttpRequest;
3738
import org.springframework.util.Assert;
3839
import org.springframework.util.CollectionUtils;
3940
import org.springframework.util.LinkedMultiValueMap;
@@ -811,18 +812,31 @@ public static boolean isSameOrigin(HttpRequest request) {
811812
if (origin == null) {
812813
return true;
813814
}
814-
UriComponents actualUrl = UriComponentsBuilder.fromHttpRequest(request).build();
815+
UriComponentsBuilder urlBuilder;
816+
if (request instanceof ServletServerHttpRequest) {
817+
// Build more efficiently if we can: we only need scheme, host, port for origin comparison
818+
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
819+
urlBuilder = new UriComponentsBuilder().
820+
scheme(servletRequest.getScheme()).
821+
host(servletRequest.getServerName()).
822+
port(servletRequest.getServerPort()).
823+
adaptFromForwardedHeaders(request.getHeaders());
824+
}
825+
else {
826+
urlBuilder = UriComponentsBuilder.fromHttpRequest(request);
827+
}
828+
UriComponents actualUrl = urlBuilder.build();
815829
UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
816830
return (actualUrl.getHost().equals(originUrl.getHost()) && getPort(actualUrl) == getPort(originUrl));
817831
}
818832

819-
private static int getPort(UriComponents component) {
820-
int port = component.getPort();
833+
private static int getPort(UriComponents uri) {
834+
int port = uri.getPort();
821835
if (port == -1) {
822-
if ("http".equals(component.getScheme()) || "ws".equals(component.getScheme())) {
836+
if ("http".equals(uri.getScheme()) || "ws".equals(uri.getScheme())) {
823837
port = 80;
824838
}
825-
else if ("https".equals(component.getScheme()) || "wss".equals(component.getScheme())) {
839+
else if ("https".equals(uri.getScheme()) || "wss".equals(uri.getScheme())) {
826840
port = 443;
827841
}
828842
}

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -406,7 +406,7 @@ private static UriComponentsBuilder fromMethodInternal(UriComponentsBuilder base
406406

407407
private static UriComponentsBuilder getBaseUrlToUse(UriComponentsBuilder baseUrl) {
408408
if (baseUrl != null) {
409-
return (UriComponentsBuilder) baseUrl.clone();
409+
return baseUrl.cloneBuilder();
410410
}
411411
else {
412412
return ServletUriComponentsBuilder.fromCurrentServletMapping();

spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -223,7 +223,7 @@ public String removePathExtension() {
223223
}
224224

225225
@Override
226-
public Object clone() {
226+
public ServletUriComponentsBuilder cloneBuilder() {
227227
return new ServletUriComponentsBuilder(this);
228228
}
229229

0 commit comments

Comments
 (0)