From bb3b1f2fe200f93253af49d413d037a6ccc4be1f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 18 Jan 2017 21:45:33 -0500 Subject: [PATCH] Additional convenience methods in UriUtils The generic encode method in UriUtils that encodes any character outside the reserved character set for a URI is meant for "strict" encoding of URI variable values. This commit adds a couple more conveninence methods that accept a Map or array of URI variable values to encode. This facilitates the use case where the URI template is assumed to be encoded while URI variables are encoded strictly to avoid any possibility for unwanted reserved characters: Map encodedUriVars = UriUtils.encodeUriVariables(uriVars); uriComponentsBuilder.build(true).expand(encodedUriVars).toUri(); Issue: SPR-14970 --- .../web/util/DefaultUriTemplateHandler.java | 25 ++------- .../springframework/web/util/UriUtils.java | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java index b6e3ea69001..8c0ad71504f 100644 --- a/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java +++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriTemplateHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -16,10 +16,8 @@ package org.springframework.web.util; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -127,10 +125,7 @@ public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler { return builder.buildAndExpand(uriVariables).encode(); } else { - Map encodedUriVars = new HashMap<>(uriVariables.size()); - for (Map.Entry entry : uriVariables.entrySet()) { - encodedUriVars.put(entry.getKey(), applyStrictEncoding(entry.getValue())); - } + Map encodedUriVars = UriUtils.encodeUriVariables(uriVariables); return builder.buildAndExpand(encodedUriVars); } } @@ -140,25 +135,11 @@ public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler { return builder.buildAndExpand(uriVariables).encode(); } else { - Object[] encodedUriVars = new Object[uriVariables.length]; - for (int i = 0; i < uriVariables.length; i++) { - encodedUriVars[i] = applyStrictEncoding(uriVariables[i]); - } + Object[] encodedUriVars = UriUtils.encodeUriVariables(uriVariables); return builder.buildAndExpand(encodedUriVars); } } - private String applyStrictEncoding(Object value) { - String stringValue = (value != null ? value.toString() : ""); - try { - return UriUtils.encode(stringValue, "UTF-8"); - } - catch (UnsupportedEncodingException ex) { - // Should never happen - throw new IllegalStateException("Failed to encode URI variable", ex); - } - } - private URI createUri(UriComponents uriComponents) { try { // Avoid further encoding (in the case of strictEncoding=true) diff --git a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java index 7cb3b76f1e2..7c010f02912 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriUtils.java @@ -18,6 +18,11 @@ package org.springframework.web.util; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; import org.springframework.util.StringUtils; @@ -165,6 +170,52 @@ public abstract class UriUtils { return HierarchicalUriComponents.encodeUriComponent(source, encoding, type); } + /** + * Encode characters outside the unreserved character set as defined in + * RFC 3986 Section 2. + *

This can be used to ensure the given String will not contain any + * characters with reserved URI meaning regardless of URI component. + * @param source the String to be encoded + * @param charset the character encoding to encode to + * @return the encoded String + */ + public static String encode(String source, Charset charset) { + HierarchicalUriComponents.Type type = HierarchicalUriComponents.Type.URI; + return HierarchicalUriComponents.encodeUriComponent(source, charset, type); + } + + /** + * Apply {@link #encode(String, String)} to the values in the given URI + * variables and return a new Map containing the encoded values. + * @param uriVariables the URI variable values to be encoded + * @return the encoded String + * @since 5.0 + */ + public static Map encodeUriVariables(Map uriVariables) { + Map result = new LinkedHashMap<>(uriVariables.size()); + uriVariables.entrySet().stream().forEach(entry -> { + String stringValue = (entry.getValue() != null ? entry.getValue().toString() : ""); + result.put(entry.getKey(), encode(stringValue, StandardCharsets.UTF_8)); + }); + return result; + } + + /** + * Apply {@link #encode(String, String)} to the values in the given URI + * variables and return a new array containing the encoded values. + * @param uriVariables the URI variable values to be encoded + * @return the encoded String + * @since 5.0 + */ + public static Object[] encodeUriVariables(Object... uriVariables) { + return Arrays.stream(uriVariables) + .map(value -> { + String stringValue = (value != null ? value.toString() : ""); + return encode(stringValue, StandardCharsets.UTF_8); + }) + .collect(Collectors.toList()).toArray(); + } + /** * Decode the given encoded URI component. *

See {@link StringUtils#uriDecode(String, Charset) for the decoding rules.