001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.util;
018
019import java.net.URI;
020import java.util.ArrayList;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.Map;
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026
027import org.springframework.http.HttpHeaders;
028import org.springframework.http.HttpRequest;
029import org.springframework.util.Assert;
030import org.springframework.util.LinkedMultiValueMap;
031import org.springframework.util.MultiValueMap;
032import org.springframework.util.ObjectUtils;
033import org.springframework.util.StringUtils;
034import org.springframework.web.util.HierarchicalUriComponents.PathComponent;
035
036/**
037 * Builder for {@link UriComponents}.
038 *
039 * <p>Typical usage involves:
040 * <ol>
041 * <li>Create a {@code UriComponentsBuilder} with one of the static factory methods
042 * (such as {@link #fromPath(String)} or {@link #fromUri(URI)})</li>
043 * <li>Set the various URI components through the respective methods ({@link #scheme(String)},
044 * {@link #userInfo(String)}, {@link #host(String)}, {@link #port(int)}, {@link #path(String)},
045 * {@link #pathSegment(String...)}, {@link #queryParam(String, Object...)}, and
046 * {@link #fragment(String)}.</li>
047 * <li>Build the {@link UriComponents} instance with the {@link #build()} method.</li>
048 * </ol>
049 *
050 * @author Arjen Poutsma
051 * @author Rossen Stoyanchev
052 * @author Phillip Webb
053 * @author Oliver Gierke
054 * @author Brian Clozel
055 * @author Sebastien Deleuze
056 * @since 3.1
057 * @see #newInstance()
058 * @see #fromPath(String)
059 * @see #fromUri(URI)
060 */
061public class UriComponentsBuilder implements Cloneable {
062
063        private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("([^&=]+)(=?)([^&]+)?");
064
065        private static final String SCHEME_PATTERN = "([^:/?#]+):";
066
067        private static final String HTTP_PATTERN = "(?i)(http|https):";
068
069        private static final String USERINFO_PATTERN = "([^@\\[/?#]*)";
070
071        private static final String HOST_IPV4_PATTERN = "[^\\[/?#:]*";
072
073        private static final String HOST_IPV6_PATTERN = "\\[[\\p{XDigit}\\:\\.]*[%\\p{Alnum}]*\\]";
074
075        private static final String HOST_PATTERN = "(" + HOST_IPV6_PATTERN + "|" + HOST_IPV4_PATTERN + ")";
076
077        private static final String PORT_PATTERN = "(\\d*(?:\\{[^/]+?\\})?)";
078
079        private static final String PATH_PATTERN = "([^?#]*)";
080
081        private static final String QUERY_PATTERN = "([^#]*)";
082
083        private static final String LAST_PATTERN = "(.*)";
084
085        // Regex patterns that matches URIs. See RFC 3986, appendix B
086        private static final Pattern URI_PATTERN = Pattern.compile(
087                        "^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
088                                        ")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");
089
090        private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
091                        "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
092                                        PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
093
094        private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?");
095
096        private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?");
097
098
099        private String scheme;
100
101        private String ssp;
102
103        private String userInfo;
104
105        private String host;
106
107        private String port;
108
109        private CompositePathComponentBuilder pathBuilder;
110
111        private LinkedMultiValueMap<String, String> queryParams = new LinkedMultiValueMap<String, String>();
112
113        private String fragment;
114
115
116        /**
117         * Default constructor. Protected to prevent direct instantiation.
118         * @see #newInstance()
119         * @see #fromPath(String)
120         * @see #fromUri(URI)
121         */
122        protected UriComponentsBuilder() {
123                this.pathBuilder = new CompositePathComponentBuilder();
124        }
125
126        /**
127         * Create a deep copy of the given UriComponentsBuilder.
128         * @param other the other builder to copy from
129         * @since 4.1.3
130         */
131        protected UriComponentsBuilder(UriComponentsBuilder other) {
132                this.scheme = other.scheme;
133                this.ssp = other.ssp;
134                this.userInfo = other.userInfo;
135                this.host = other.host;
136                this.port = other.port;
137                this.pathBuilder = other.pathBuilder.cloneBuilder();
138                this.queryParams = other.queryParams.deepCopy();
139                this.fragment = other.fragment;
140        }
141
142
143        // Factory methods
144
145        /**
146         * Create a new, empty builder.
147         * @return the new {@code UriComponentsBuilder}
148         */
149        public static UriComponentsBuilder newInstance() {
150                return new UriComponentsBuilder();
151        }
152
153        /**
154         * Create a builder that is initialized with the given path.
155         * @param path the path to initialize with
156         * @return the new {@code UriComponentsBuilder}
157         */
158        public static UriComponentsBuilder fromPath(String path) {
159                UriComponentsBuilder builder = new UriComponentsBuilder();
160                builder.path(path);
161                return builder;
162        }
163
164        /**
165         * Create a builder that is initialized from the given {@code URI}.
166         * <p><strong>Note:</strong> the components in the resulting builder will be
167         * in fully encoded (raw) form and further changes must also supply values
168         * that are fully encoded, for example via methods in {@link UriUtils}.
169         * In addition please use {@link #build(boolean)} with a value of "true" to
170         * build the {@link UriComponents} instance in order to indicate that the
171         * components are encoded.
172         * @param uri the URI to initialize with
173         * @return the new {@code UriComponentsBuilder}
174         */
175        public static UriComponentsBuilder fromUri(URI uri) {
176                UriComponentsBuilder builder = new UriComponentsBuilder();
177                builder.uri(uri);
178                return builder;
179        }
180
181        /**
182         * Create a builder that is initialized with the given URI string.
183         * <p><strong>Note:</strong> The presence of reserved characters can prevent
184         * correct parsing of the URI string. For example if a query parameter
185         * contains {@code '='} or {@code '&'} characters, the query string cannot
186         * be parsed unambiguously. Such values should be substituted for URI
187         * variables to enable correct parsing:
188         * <pre class="code">
189         * String uriString = &quot;/hotels/42?filter={value}&quot;;
190         * UriComponentsBuilder.fromUriString(uriString).buildAndExpand(&quot;hot&amp;cold&quot;);
191         * </pre>
192         * @param uri the URI string to initialize with
193         * @return the new {@code UriComponentsBuilder}
194         */
195        public static UriComponentsBuilder fromUriString(String uri) {
196                Assert.notNull(uri, "URI must not be null");
197                Matcher matcher = URI_PATTERN.matcher(uri);
198                if (matcher.matches()) {
199                        UriComponentsBuilder builder = new UriComponentsBuilder();
200                        String scheme = matcher.group(2);
201                        String userInfo = matcher.group(5);
202                        String host = matcher.group(6);
203                        String port = matcher.group(8);
204                        String path = matcher.group(9);
205                        String query = matcher.group(11);
206                        String fragment = matcher.group(13);
207                        boolean opaque = false;
208                        if (StringUtils.hasLength(scheme)) {
209                                String rest = uri.substring(scheme.length());
210                                if (!rest.startsWith(":/")) {
211                                        opaque = true;
212                                }
213                        }
214                        builder.scheme(scheme);
215                        if (opaque) {
216                                String ssp = uri.substring(scheme.length() + 1);
217                                if (StringUtils.hasLength(fragment)) {
218                                        ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1));
219                                }
220                                builder.schemeSpecificPart(ssp);
221                        }
222                        else {
223                                builder.userInfo(userInfo);
224                                builder.host(host);
225                                if (StringUtils.hasLength(port)) {
226                                        builder.port(port);
227                                }
228                                builder.path(path);
229                                builder.query(query);
230                        }
231                        if (StringUtils.hasText(fragment)) {
232                                builder.fragment(fragment);
233                        }
234                        return builder;
235                }
236                else {
237                        throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
238                }
239        }
240
241        /**
242         * Create a URI components builder from the given HTTP URL String.
243         * <p><strong>Note:</strong> The presence of reserved characters can prevent
244         * correct parsing of the URI string. For example if a query parameter
245         * contains {@code '='} or {@code '&'} characters, the query string cannot
246         * be parsed unambiguously. Such values should be substituted for URI
247         * variables to enable correct parsing:
248         * <pre class="code">
249         * String urlString = &quot;https://example.com/hotels/42?filter={value}&quot;;
250         * UriComponentsBuilder.fromHttpUrl(urlString).buildAndExpand(&quot;hot&amp;cold&quot;);
251         * </pre>
252         * @param httpUrl the source URI
253         * @return the URI components of the URI
254         */
255        public static UriComponentsBuilder fromHttpUrl(String httpUrl) {
256                Assert.notNull(httpUrl, "HTTP URL must not be null");
257                Matcher matcher = HTTP_URL_PATTERN.matcher(httpUrl);
258                if (matcher.matches()) {
259                        UriComponentsBuilder builder = new UriComponentsBuilder();
260                        String scheme = matcher.group(1);
261                        builder.scheme(scheme != null ? scheme.toLowerCase() : null);
262                        builder.userInfo(matcher.group(4));
263                        String host = matcher.group(5);
264                        if (StringUtils.hasLength(scheme) && !StringUtils.hasLength(host)) {
265                                throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
266                        }
267                        builder.host(host);
268                        String port = matcher.group(7);
269                        if (StringUtils.hasLength(port)) {
270                                builder.port(port);
271                        }
272                        builder.path(matcher.group(8));
273                        builder.query(matcher.group(10));
274                        return builder;
275                }
276                else {
277                        throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
278                }
279        }
280
281        /**
282         * Create a new {@code UriComponents} object from the URI associated with
283         * the given HttpRequest while also overlaying with values from the headers
284         * "Forwarded" (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>),
285         * or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if
286         * "Forwarded" is not found.
287         * <p><strong>Note:</strong> this method uses values from forwarded headers,
288         * if present, in order to reflect the client-originated protocol and address.
289         * Consider using the {@code ForwardedHeaderFilter} in order to choose from a
290         * central place whether to extract and use, or to discard such headers.
291         * See the Spring Framework reference for more on this filter.
292         * @param request the source request
293         * @return the URI components of the URI
294         * @since 4.1.5
295         */
296        public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
297                return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders());
298        }
299
300        /**
301         * Create an instance by parsing the "Origin" header of an HTTP request.
302         * @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
303         */
304        public static UriComponentsBuilder fromOriginHeader(String origin) {
305                Matcher matcher = URI_PATTERN.matcher(origin);
306                if (matcher.matches()) {
307                        UriComponentsBuilder builder = new UriComponentsBuilder();
308                        String scheme = matcher.group(2);
309                        String host = matcher.group(6);
310                        String port = matcher.group(8);
311                        if (StringUtils.hasLength(scheme)) {
312                                builder.scheme(scheme);
313                        }
314                        builder.host(host);
315                        if (StringUtils.hasLength(port)) {
316                                builder.port(port);
317                        }
318                        return builder;
319                }
320                else {
321                        throw new IllegalArgumentException("[" + origin + "] is not a valid \"Origin\" header value");
322                }
323        }
324
325
326        // build methods
327
328        /**
329         * Build a {@code UriComponents} instance from the various components contained in this builder.
330         * @return the URI components
331         */
332        public UriComponents build() {
333                return build(false);
334        }
335
336        /**
337         * Variant of {@link #build()} to create a {@link UriComponents} instance
338         * when components are already fully encoded. This is useful for example if
339         * the builder was created via {@link UriComponentsBuilder#fromUri(URI)}.
340         * @param encoded whether the components in this builder are already encoded
341         * @return the URI components
342         * @throws IllegalArgumentException if any of the components contain illegal
343         * characters that should have been encoded.
344         */
345        public UriComponents build(boolean encoded) {
346                if (this.ssp != null) {
347                        return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
348                }
349                else {
350                        return new HierarchicalUriComponents(this.scheme, this.userInfo, this.host, this.port,
351                                        this.pathBuilder.build(), this.queryParams, this.fragment, encoded, true);
352                }
353        }
354
355        /**
356         * Build a {@code UriComponents} instance and replaces URI template variables
357         * with the values from a map. This is a shortcut method which combines
358         * calls to {@link #build()} and then {@link UriComponents#expand(Map)}.
359         * @param uriVariables the map of URI variables
360         * @return the URI components with expanded values
361         */
362        public UriComponents buildAndExpand(Map<String, ?> uriVariables) {
363                return build(false).expand(uriVariables);
364        }
365
366        /**
367         * Build a {@code UriComponents} instance and replaces URI template variables
368         * with the values from an array. This is a shortcut method which combines
369         * calls to {@link #build()} and then {@link UriComponents#expand(Object...)}.
370         * @param uriVariableValues URI variable values
371         * @return the URI components with expanded values
372         */
373        public UriComponents buildAndExpand(Object... uriVariableValues) {
374                return build(false).expand(uriVariableValues);
375        }
376
377        /**
378         * Build a URI String. This is a shortcut method which combines calls
379         * to {@link #build()}, then {@link UriComponents#encode()} and finally
380         * {@link UriComponents#toUriString()}.
381         * @since 4.1
382         * @see UriComponents#toUriString()
383         */
384        public String toUriString() {
385                return build(false).encode().toUriString();
386        }
387
388
389        // Instance methods
390
391        /**
392         * Initialize components of this builder from components of the given URI.
393         * @param uri the URI
394         * @return this UriComponentsBuilder
395         */
396        public UriComponentsBuilder uri(URI uri) {
397                Assert.notNull(uri, "URI must not be null");
398                this.scheme = uri.getScheme();
399                if (uri.isOpaque()) {
400                        this.ssp = uri.getRawSchemeSpecificPart();
401                        resetHierarchicalComponents();
402                }
403                else {
404                        if (uri.getRawUserInfo() != null) {
405                                this.userInfo = uri.getRawUserInfo();
406                        }
407                        if (uri.getHost() != null) {
408                                this.host = uri.getHost();
409                        }
410                        if (uri.getPort() != -1) {
411                                this.port = String.valueOf(uri.getPort());
412                        }
413                        if (StringUtils.hasLength(uri.getRawPath())) {
414                                this.pathBuilder = new CompositePathComponentBuilder(uri.getRawPath());
415                        }
416                        if (StringUtils.hasLength(uri.getRawQuery())) {
417                                this.queryParams.clear();
418                                query(uri.getRawQuery());
419                        }
420                        resetSchemeSpecificPart();
421                }
422                if (uri.getRawFragment() != null) {
423                        this.fragment = uri.getRawFragment();
424                }
425                return this;
426        }
427
428        /**
429         * Set or append individual URI components of this builder from the values
430         * of the given {@link UriComponents} instance.
431         * <p>For the semantics of each component (i.e. set vs append) check the
432         * builder methods on this class. For example {@link #host(String)} sets
433         * while {@link #path(String)} appends.
434         * @param uriComponents the UriComponents to copy from
435         * @return this UriComponentsBuilder
436         */
437        public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
438                Assert.notNull(uriComponents, "UriComponents must not be null");
439                uriComponents.copyToUriComponentsBuilder(this);
440                return this;
441        }
442
443        /**
444         * Set the URI scheme. The given scheme may contain URI template variables,
445         * and may also be {@code null} to clear the scheme of this builder.
446         * @param scheme the URI scheme
447         * @return this UriComponentsBuilder
448         */
449        public UriComponentsBuilder scheme(String scheme) {
450                this.scheme = scheme;
451                return this;
452        }
453
454        /**
455         * Set the URI scheme-specific-part. When invoked, this method overwrites
456         * {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
457         * {@linkplain #port(int) port}, {@linkplain #path(String) path}, and
458         * {@link #query(String) query}.
459         * @param ssp the URI scheme-specific-part, may contain URI template parameters
460         * @return this UriComponentsBuilder
461         */
462        public UriComponentsBuilder schemeSpecificPart(String ssp) {
463                this.ssp = ssp;
464                resetHierarchicalComponents();
465                return this;
466        }
467
468        /**
469         * Set the URI user info. The given user info may contain URI template variables,
470         * and may also be {@code null} to clear the user info of this builder.
471         * @param userInfo the URI user info
472         * @return this UriComponentsBuilder
473         */
474        public UriComponentsBuilder userInfo(String userInfo) {
475                this.userInfo = userInfo;
476                resetSchemeSpecificPart();
477                return this;
478        }
479
480        /**
481         * Set the URI host. The given host may contain URI template variables,
482         * and may also be {@code null} to clear the host of this builder.
483         * @param host the URI host
484         * @return this UriComponentsBuilder
485         */
486        public UriComponentsBuilder host(String host) {
487                this.host = host;
488                resetSchemeSpecificPart();
489                return this;
490        }
491
492        /**
493         * Set the URI port. Passing {@code -1} will clear the port of this builder.
494         * @param port the URI port
495         * @return this UriComponentsBuilder
496         */
497        public UriComponentsBuilder port(int port) {
498                Assert.isTrue(port >= -1, "Port must be >= -1");
499                this.port = String.valueOf(port);
500                resetSchemeSpecificPart();
501                return this;
502        }
503
504        /**
505         * Set the URI port. Use this method only when the port needs to be
506         * parameterized with a URI variable. Otherwise use {@link #port(int)}.
507         * Passing {@code null} will clear the port of this builder.
508         * @param port the URI port
509         * @return this UriComponentsBuilder
510         */
511        public UriComponentsBuilder port(String port) {
512                this.port = port;
513                resetSchemeSpecificPart();
514                return this;
515        }
516
517        /**
518         * Append the given path to the existing path of this builder.
519         * The given path may contain URI template variables.
520         * @param path the URI path
521         * @return this UriComponentsBuilder
522         */
523        public UriComponentsBuilder path(String path) {
524                this.pathBuilder.addPath(path);
525                resetSchemeSpecificPart();
526                return this;
527        }
528
529        /**
530         * Append path segments to the existing path. Each path segment may contain
531         * URI template variables and should not contain any slashes.
532         * Use {@code path("/")} subsequently to ensure a trailing slash.
533         * @param pathSegments the URI path segments
534         * @return this UriComponentsBuilder
535         */
536        public UriComponentsBuilder pathSegment(String... pathSegments) throws IllegalArgumentException {
537                this.pathBuilder.addPathSegments(pathSegments);
538                resetSchemeSpecificPart();
539                return this;
540        }
541
542        /**
543         * Set the path of this builder overriding all existing path and path segment values.
544         * @param path the URI path (a {@code null} value results in an empty path)
545         * @return this UriComponentsBuilder
546         */
547        public UriComponentsBuilder replacePath(String path) {
548                this.pathBuilder = new CompositePathComponentBuilder(path);
549                resetSchemeSpecificPart();
550                return this;
551        }
552
553        /**
554         * Append the given query to the existing query of this builder.
555         * The given query may contain URI template variables.
556         * <p><strong>Note:</strong> The presence of reserved characters can prevent
557         * correct parsing of the URI string. For example if a query parameter
558         * contains {@code '='} or {@code '&'} characters, the query string cannot
559         * be parsed unambiguously. Such values should be substituted for URI
560         * variables to enable correct parsing:
561         * <pre class="code">
562         * UriComponentsBuilder.fromUriString(&quot;/hotels/42&quot;)
563         *      .query(&quot;filter={value}&quot;)
564         *      .buildAndExpand(&quot;hot&amp;cold&quot;);
565         * </pre>
566         * @param query the query string
567         * @return this UriComponentsBuilder
568         */
569        public UriComponentsBuilder query(String query) {
570                if (query != null) {
571                        Matcher matcher = QUERY_PARAM_PATTERN.matcher(query);
572                        while (matcher.find()) {
573                                String name = matcher.group(1);
574                                String eq = matcher.group(2);
575                                String value = matcher.group(3);
576                                queryParam(name, (value != null ? value : (StringUtils.hasLength(eq) ? "" : null)));
577                        }
578                }
579                else {
580                        this.queryParams.clear();
581                }
582                resetSchemeSpecificPart();
583                return this;
584        }
585
586        /**
587         * Set the query of this builder overriding all existing query parameters.
588         * @param query the query string; a {@code null} value removes all query parameters.
589         * @return this UriComponentsBuilder
590         */
591        public UriComponentsBuilder replaceQuery(String query) {
592                this.queryParams.clear();
593                query(query);
594                resetSchemeSpecificPart();
595                return this;
596        }
597
598        /**
599         * Append the given query parameter to the existing query parameters. The
600         * given name or any of the values may contain URI template variables. If no
601         * values are given, the resulting URI will contain the query parameter name
602         * only (i.e. {@code ?foo} instead of {@code ?foo=bar}).
603         * @param name the query parameter name
604         * @param values the query parameter values
605         * @return this UriComponentsBuilder
606         */
607        public UriComponentsBuilder queryParam(String name, Object... values) {
608                Assert.notNull(name, "Name must not be null");
609                if (!ObjectUtils.isEmpty(values)) {
610                        for (Object value : values) {
611                                String valueAsString = (value != null ? value.toString() : null);
612                                this.queryParams.add(name, valueAsString);
613                        }
614                }
615                else {
616                        this.queryParams.add(name, null);
617                }
618                resetSchemeSpecificPart();
619                return this;
620        }
621
622        /**
623         * Add the given query parameters.
624         * @param params the params
625         * @return this UriComponentsBuilder
626         * @since 4.0
627         */
628        public UriComponentsBuilder queryParams(MultiValueMap<String, String> params) {
629                if (params != null) {
630                        this.queryParams.putAll(params);
631                }
632                return this;
633        }
634
635        /**
636         * Set the query parameter values overriding all existing query values for
637         * the same parameter. If no values are given, the query parameter is removed.
638         * @param name the query parameter name
639         * @param values the query parameter values
640         * @return this UriComponentsBuilder
641         */
642        public UriComponentsBuilder replaceQueryParam(String name, Object... values) {
643                Assert.notNull(name, "Name must not be null");
644                this.queryParams.remove(name);
645                if (!ObjectUtils.isEmpty(values)) {
646                        queryParam(name, values);
647                }
648                resetSchemeSpecificPart();
649                return this;
650        }
651
652        /**
653         * Set the query parameter values overriding all existing query values.
654         * @param params the query parameter name
655         * @return this UriComponentsBuilder
656         * @since 4.2
657         */
658        public UriComponentsBuilder replaceQueryParams(MultiValueMap<String, String> params) {
659                this.queryParams.clear();
660                if (params != null) {
661                        this.queryParams.putAll(params);
662                }
663                return this;
664        }
665
666        /**
667         * Set the URI fragment. The given fragment may contain URI template variables,
668         * and may also be {@code null} to clear the fragment of this builder.
669         * @param fragment the URI fragment
670         * @return this UriComponentsBuilder
671         */
672        public UriComponentsBuilder fragment(String fragment) {
673                if (fragment != null) {
674                        Assert.hasLength(fragment, "Fragment must not be empty");
675                        this.fragment = fragment;
676                }
677                else {
678                        this.fragment = null;
679                }
680                return this;
681        }
682
683        /**
684         * Adapt this builder's scheme+host+port from the given headers, specifically
685         * "Forwarded" (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>,
686         * or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if
687         * "Forwarded" is not found.
688         * @param headers the HTTP headers to consider
689         * @return this UriComponentsBuilder
690         * @since 4.2.7
691         */
692        UriComponentsBuilder adaptFromForwardedHeaders(HttpHeaders headers) {
693                try {
694                        String forwardedHeader = headers.getFirst("Forwarded");
695                        if (StringUtils.hasText(forwardedHeader)) {
696                                String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
697                                Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwardedToUse);
698                                if (matcher.find()) {
699                                        scheme(matcher.group(1).trim());
700                                        port(null);
701                                }
702                                matcher = FORWARDED_HOST_PATTERN.matcher(forwardedToUse);
703                                if (matcher.find()) {
704                                        adaptForwardedHost(matcher.group(1).trim());
705                                }
706                        }
707                        else {
708                                String protocolHeader = headers.getFirst("X-Forwarded-Proto");
709                                if (StringUtils.hasText(protocolHeader)) {
710                                        scheme(StringUtils.tokenizeToStringArray(protocolHeader, ",")[0]);
711                                        port(null);
712                                }
713
714                                String hostHeader = headers.getFirst("X-Forwarded-Host");
715                                if (StringUtils.hasText(hostHeader)) {
716                                        adaptForwardedHost(StringUtils.tokenizeToStringArray(hostHeader, ",")[0]);
717                                }
718
719                                String portHeader = headers.getFirst("X-Forwarded-Port");
720                                if (StringUtils.hasText(portHeader)) {
721                                        port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0]));
722                                }
723                        }
724                }
725                catch (NumberFormatException ex) {
726                        throw new IllegalArgumentException("Failed to parse a port from \"forwarded\"-type headers. " +
727                                        "If not behind a trusted proxy, consider using ForwardedHeaderFilter " +
728                                        "with the removeOnly=true. Request headers: " + headers);
729                }
730
731                if (this.scheme != null && ((this.scheme.equals("http") && "80".equals(this.port)) ||
732                                (this.scheme.equals("https") && "443".equals(this.port)))) {
733                        port(null);
734                }
735
736                return this;
737        }
738
739        private void adaptForwardedHost(String hostToUse) {
740                int portSeparatorIdx = hostToUse.lastIndexOf(':');
741                if (portSeparatorIdx > hostToUse.lastIndexOf(']')) {
742                        host(hostToUse.substring(0, portSeparatorIdx));
743                        port(Integer.parseInt(hostToUse.substring(portSeparatorIdx + 1)));
744                }
745                else {
746                        host(hostToUse);
747                        port(null);
748                }
749        }
750
751        private void resetHierarchicalComponents() {
752                this.userInfo = null;
753                this.host = null;
754                this.port = null;
755                this.pathBuilder = new CompositePathComponentBuilder();
756                this.queryParams.clear();
757        }
758
759        private void resetSchemeSpecificPart() {
760                this.ssp = null;
761        }
762
763
764        /**
765         * Public declaration of Object's {@code clone()} method.
766         * Delegates to {@link #cloneBuilder()}.
767         */
768        @Override
769        public Object clone() {
770                return cloneBuilder();
771        }
772
773        /**
774         * Clone this {@code UriComponentsBuilder}.
775         * @return the cloned {@code UriComponentsBuilder} object
776         * @since 4.2.7
777         */
778        public UriComponentsBuilder cloneBuilder() {
779                return new UriComponentsBuilder(this);
780        }
781
782
783        private interface PathComponentBuilder {
784
785                PathComponent build();
786
787                PathComponentBuilder cloneBuilder();
788        }
789
790
791        private static class CompositePathComponentBuilder implements PathComponentBuilder {
792
793                private final LinkedList<PathComponentBuilder> builders = new LinkedList<PathComponentBuilder>();
794
795                public CompositePathComponentBuilder() {
796                }
797
798                public CompositePathComponentBuilder(String path) {
799                        addPath(path);
800                }
801
802                public void addPathSegments(String... pathSegments) {
803                        if (!ObjectUtils.isEmpty(pathSegments)) {
804                                PathSegmentComponentBuilder psBuilder = getLastBuilder(PathSegmentComponentBuilder.class);
805                                FullPathComponentBuilder fpBuilder = getLastBuilder(FullPathComponentBuilder.class);
806                                if (psBuilder == null) {
807                                        psBuilder = new PathSegmentComponentBuilder();
808                                        this.builders.add(psBuilder);
809                                        if (fpBuilder != null) {
810                                                fpBuilder.removeTrailingSlash();
811                                        }
812                                }
813                                psBuilder.append(pathSegments);
814                        }
815                }
816
817                public void addPath(String path) {
818                        if (StringUtils.hasText(path)) {
819                                PathSegmentComponentBuilder psBuilder = getLastBuilder(PathSegmentComponentBuilder.class);
820                                FullPathComponentBuilder fpBuilder = getLastBuilder(FullPathComponentBuilder.class);
821                                if (psBuilder != null) {
822                                        path = path.startsWith("/") ? path : "/" + path;
823                                }
824                                if (fpBuilder == null) {
825                                        fpBuilder = new FullPathComponentBuilder();
826                                        this.builders.add(fpBuilder);
827                                }
828                                fpBuilder.append(path);
829                        }
830                }
831
832                @SuppressWarnings("unchecked")
833                private <T> T getLastBuilder(Class<T> builderClass) {
834                        if (!this.builders.isEmpty()) {
835                                PathComponentBuilder last = this.builders.getLast();
836                                if (builderClass.isInstance(last)) {
837                                        return (T) last;
838                                }
839                        }
840                        return null;
841                }
842
843                @Override
844                public PathComponent build() {
845                        int size = this.builders.size();
846                        List<PathComponent> components = new ArrayList<PathComponent>(size);
847                        for (PathComponentBuilder componentBuilder : this.builders) {
848                                PathComponent pathComponent = componentBuilder.build();
849                                if (pathComponent != null) {
850                                        components.add(pathComponent);
851                                }
852                        }
853                        if (components.isEmpty()) {
854                                return HierarchicalUriComponents.NULL_PATH_COMPONENT;
855                        }
856                        if (components.size() == 1) {
857                                return components.get(0);
858                        }
859                        return new HierarchicalUriComponents.PathComponentComposite(components);
860                }
861
862                @Override
863                public CompositePathComponentBuilder cloneBuilder() {
864                        CompositePathComponentBuilder compositeBuilder = new CompositePathComponentBuilder();
865                        for (PathComponentBuilder builder : this.builders) {
866                                compositeBuilder.builders.add(builder.cloneBuilder());
867                        }
868                        return compositeBuilder;
869                }
870        }
871
872
873        private static class FullPathComponentBuilder implements PathComponentBuilder {
874
875                private final StringBuilder path = new StringBuilder();
876
877                public void append(String path) {
878                        this.path.append(path);
879                }
880
881                @Override
882                public PathComponent build() {
883                        if (this.path.length() == 0) {
884                                return null;
885                        }
886                        String path = this.path.toString();
887                        while (true) {
888                                int index = path.indexOf("//");
889                                if (index == -1) {
890                                        break;
891                                }
892                                path = path.substring(0, index) + path.substring(index + 1);
893                        }
894                        return new HierarchicalUriComponents.FullPathComponent(path);
895                }
896
897                public void removeTrailingSlash() {
898                        int index = this.path.length() - 1;
899                        if (this.path.charAt(index) == '/') {
900                                this.path.deleteCharAt(index);
901                        }
902                }
903
904                @Override
905                public FullPathComponentBuilder cloneBuilder() {
906                        FullPathComponentBuilder builder = new FullPathComponentBuilder();
907                        builder.append(this.path.toString());
908                        return builder;
909                }
910        }
911
912
913        private static class PathSegmentComponentBuilder implements PathComponentBuilder {
914
915                private final List<String> pathSegments = new LinkedList<String>();
916
917                public void append(String... pathSegments) {
918                        for (String pathSegment : pathSegments) {
919                                if (StringUtils.hasText(pathSegment)) {
920                                        this.pathSegments.add(pathSegment);
921                                }
922                        }
923                }
924
925                @Override
926                public PathComponent build() {
927                        return (this.pathSegments.isEmpty() ? null :
928                                        new HierarchicalUriComponents.PathSegmentComponent(this.pathSegments));
929                }
930
931                @Override
932                public PathSegmentComponentBuilder cloneBuilder() {
933                        PathSegmentComponentBuilder builder = new PathSegmentComponentBuilder();
934                        builder.pathSegments.addAll(this.pathSegments);
935                        return builder;
936                }
937        }
938
939}