JAX-RS UriBuilder为何不支持双重编码?
问题场景
以下是一段生成重定向URI的代码:
import javax.ws.rs.core.UriBuilder; import java.net.URI; public class AnyParamRedirecter { /** * 生成指向target.com的URI,包含一个名为"extra"的查询参数 */ public URI generateNestedRedirectUrl(String extraParam) { return UriBuilder.fromPath("http://target.com") // 对所有特殊字符编码,但已编码的内容除外 .queryParam("extra", extraParam) .build(); } }
这段代码存在问题:当参数extraParam包含已编码但仍需双重编码的值时,queryParam()方法无法正确处理(注:queryParam仅对需要编码的字符执行单次编码)。
示例验证
下面的测试用例可以复现该问题:
@Test public void doubleEncodeTest() { final String deeplyEmbeddedUrlEncoded = URLEncoder.encode("http://other2.com?x=1?y=2", StandardCharsets.UTF_8); assertEquals(deeplyEmbeddedUrlEncoded, "http%3A%2F%2Fother2.com%3Fx%3D1%3Fy%3D2"); final String embeddedUrl = "http://other.com?c=" + deeplyEmbeddedUrlEncoded; var redirecter = new AnyParamRedirecter(); URI result = redirecter.generateNestedRedirectUrl(embeddedUrl); String nestedUrl = result.getQuery().substring("extra=".length()); // 断言输出与输入一致 assertEquals(embeddedUrl, nestedUrl); }
执行结果:
Expected :http://other.com?c=http%3A%2F%2Fother2.com%3Fx%3D1%3Fy%3D2 Actual :http://other.com?c=http://other2.com?x=1?y=2
可以看到,原本属于other2.com的x、y参数被错误解析为other.com的参数,根源是已编码的内容被queryParam自动解码了。
疑问与分析
虽然可以通过queryParam("extra", URLEncoder.encode(extraParam, StandardCharsets.UTF_8))强制双重编码来规避问题,但让人困惑的是:为何queryParam方法会拒绝对已百分号编码的值重新编码?
查看javax.ws.rs.core.UriBuilder的Javadoc说明:
URI模板感知的工具类,用于从组件构建URI。有关URI模板的说明请参考{@link javax.ws.rs.Path#value}。
构建器方法会根据
application/x-www-form-urlencoded媒体类型规则,对对应URI组件中不允许出现的字符执行上下文编码(针对查询参数)...注意,仅对特定组件中不允许的字符进行编码,例如,传递给
path方法的路径可以包含矩阵参数或多个路径段,因为分隔符是合法字符,不会被编码。已百分号编码的值在允许的情况下也会被识别,不会被双重编码。
这种“从组件构建URI”却“不对已百分号编码值双重编码”的设计看起来存在矛盾,而且queryParam方法的Javadoc并没有单独明确这一行为,这种设计逻辑的出发点是什么?
内容的提问来源于stack exchange,提问作者tkruse

