From c5870141eb285fdd21417f2bf47e435bc27c279e Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 26 Dec 2019 14:21:49 -0500 Subject: [PATCH 1/5] Ensure Iterable values are encoded before template expansion Fixes #1123, Fixes #1133, Fixes #1102, Fixes #1028 Ensures that all expressions are fully-encoded before being manipulated during template expansion. This allows parameters to include reserved values and result in properly encoded results. Additionally, `Iterable` values are now handled in accordance with RFC 6570 allowing for the specified `CollectionFormat` to be applied and empty parameters to be expanded correctly as this is the main use case that exhibited this issue. --- .../src/main/java/feign/CollectionFormat.java | 8 +- core/src/main/java/feign/RequestTemplate.java | 5 +- .../main/java/feign/template/Expression.java | 2 +- .../main/java/feign/template/Expressions.java | 55 ++++-- .../java/feign/template/QueryTemplate.java | 112 ++++++----- .../main/java/feign/template/Template.java | 107 ++++------- .../main/java/feign/template/UriUtils.java | 176 ++++++------------ .../test/java/feign/RequestTemplateTest.java | 4 +- .../feign/template/QueryTemplateTest.java | 13 +- .../java/feign/template/UriUtilsTest.java | 71 ++----- 10 files changed, 229 insertions(+), 324 deletions(-) diff --git a/core/src/main/java/feign/CollectionFormat.java b/core/src/main/java/feign/CollectionFormat.java index 14909da41..5623c5aa6 100644 --- a/core/src/main/java/feign/CollectionFormat.java +++ b/core/src/main/java/feign/CollectionFormat.java @@ -74,21 +74,21 @@ public CharSequence join(String field, Collection values, Charset charse if (separator == null) { // exploded builder.append(valueCount++ == 0 ? "" : "&"); - builder.append(UriUtils.queryEncode(field, charset)); + builder.append(UriUtils.encode(field, charset)); if (value != null) { builder.append('='); - builder.append(UriUtils.queryEncode(value, charset)); + builder.append(UriUtils.encode(value, charset)); } } else { // delimited with a separator character if (builder.length() == 0) { - builder.append(UriUtils.queryEncode(field, charset)); + builder.append(UriUtils.encode(field, charset)); } if (value == null) { continue; } builder.append(valueCount++ == 0 ? "=" : separator); - builder.append(UriUtils.queryEncode(value, charset)); + builder.append(UriUtils.encode(value, charset)); } } return builder; diff --git a/core/src/main/java/feign/RequestTemplate.java b/core/src/main/java/feign/RequestTemplate.java index bf089181c..facf969f2 100644 --- a/core/src/main/java/feign/RequestTemplate.java +++ b/core/src/main/java/feign/RequestTemplate.java @@ -164,7 +164,10 @@ public RequestTemplate resolve(Map variables) { this.uriTemplate = UriTemplate.create("", !this.decodeSlash, this.charset); } - uri.append(this.uriTemplate.expand(variables)); + String expanded = this.uriTemplate.expand(variables); + if (expanded != null) { + uri.append(expanded); + } /* * for simplicity, combine the queries into the uri and use the resulting uri to seed the diff --git a/core/src/main/java/feign/template/Expression.java b/core/src/main/java/feign/template/Expression.java index 3040aa1c3..f4852df8c 100644 --- a/core/src/main/java/feign/template/Expression.java +++ b/core/src/main/java/feign/template/Expression.java @@ -13,8 +13,8 @@ */ package feign.template; +import feign.CollectionFormat; import java.util.Optional; -import java.util.regex.Matcher; import java.util.regex.Pattern; /** diff --git a/core/src/main/java/feign/template/Expressions.java b/core/src/main/java/feign/template/Expressions.java index 96dfe02dc..87592621a 100644 --- a/core/src/main/java/feign/template/Expressions.java +++ b/core/src/main/java/feign/template/Expressions.java @@ -13,16 +13,13 @@ */ package feign.template; -import feign.Util; -import feign.template.UriUtils.FragmentType; -import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import feign.Util; public final class Expressions { private static Map> expressions; @@ -42,7 +39,7 @@ public final class Expressions { SimpleExpression.class); } - public static Expression create(final String value, final FragmentType type) { + public static Expression create(final String value) { /* remove the start and end braces */ final String expression = stripBraces(value); @@ -77,7 +74,7 @@ public static Expression create(final String value, final FragmentType type) { } } - return new SimpleExpression(variableName, variablePattern, type); + return new SimpleExpression(variableName, variablePattern); } private static String stripBraces(String expression) { @@ -96,26 +93,19 @@ private static String stripBraces(String expression) { */ static class SimpleExpression extends Expression { - private final FragmentType type; - - SimpleExpression(String expression, String pattern, FragmentType type) { + SimpleExpression(String expression, String pattern) { super(expression, pattern); - this.type = type; } String encode(Object value) { - return UriUtils.encodeReserved(value.toString(), type, Util.UTF_8); + return UriUtils.encode(value.toString(), Util.UTF_8); } @Override String expand(Object variable, boolean encode) { StringBuilder expanded = new StringBuilder(); if (Iterable.class.isAssignableFrom(variable.getClass())) { - List items = new ArrayList<>(); - for (Object item : ((Iterable) variable)) { - items.add((encode) ? encode(item) : item.toString()); - } - expanded.append(String.join(Template.COLLECTION_DELIMITER, items)); + expanded.append(this.expandIterable((Iterable) variable)); } else { expanded.append((encode) ? encode(variable) : variable); } @@ -128,5 +118,38 @@ String expand(Object variable, boolean encode) { } return result; } + + + private String expandIterable(Iterable values) { + StringBuilder result = new StringBuilder(); + for (Object value : values) { + if (value == null) { + /* skip */ + continue; + } + + /* expand the value */ + String expanded = this.encode(value); + if (expanded.isEmpty()) { + /* always append the separator */ + result.append(","); + } else { + if (result.length() != 0) { + if (!result.toString().equalsIgnoreCase(",")) { + result.append(","); + } + } + result.append(expanded); + } + } + + if (result.length() == 0) { + /* completely unresolved */ + return null; + } + + /* return the expanded value */ + return result.toString(); + } } } diff --git a/core/src/main/java/feign/template/QueryTemplate.java b/core/src/main/java/feign/template/QueryTemplate.java index c67735272..19e267c7d 100644 --- a/core/src/main/java/feign/template/QueryTemplate.java +++ b/core/src/main/java/feign/template/QueryTemplate.java @@ -13,26 +13,29 @@ */ package feign.template; -import feign.CollectionFormat; -import feign.Util; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Iterator; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import feign.CollectionFormat; +import feign.Util; +import feign.template.Template.EncodingOptions; +import feign.template.Template.ExpansionOptions; /** * Template for a Query String parameter. */ -public final class QueryTemplate extends Template { +public final class QueryTemplate { private static final String UNDEF = "undef"; - private List values; + private List