Ban call of URLEncoder.encode/URLDecoder.decode(String,String)

Add ArchUnit rules to ban the use of `URLEncoder` calls with String
charsets and use `Charset` calls instead.

See gh-38740
This commit is contained in:
Yanming Zhou 2023-12-12 12:01:01 +08:00 committed by Phillip Webb
parent ae79a7c00f
commit 8cb8999772
6 changed files with 33 additions and 17 deletions

View File

@ -18,6 +18,8 @@ package org.springframework.boot.build.architecture;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.List; import java.util.List;
@ -60,6 +62,7 @@ import org.gradle.api.tasks.TaskAction;
* {@link Task} that checks for architecture problems. * {@link Task} that checks for architecture problems.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Yanming Zhou
*/ */
public abstract class ArchitectureCheck extends DefaultTask { public abstract class ArchitectureCheck extends DefaultTask {
@ -71,7 +74,8 @@ public abstract class ArchitectureCheck extends DefaultTask {
allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(), allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(),
allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(), allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(),
noClassesShouldCallStepVerifierStepVerifyComplete(), noClassesShouldCallStepVerifierStepVerifyComplete(),
noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList()); noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList(),
noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding());
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList())); getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
} }
@ -190,6 +194,20 @@ public abstract class ArchitectureCheck extends DefaultTask {
.because("java.util.stream.Stream.toList() should be used instead"); .because("java.util.stream.Stream.toList() should be used instead");
} }
private ArchRule noClassesShouldCallURLEncoderWithStringEncoding() {
return ArchRuleDefinition.noClasses()
.should()
.callMethod(URLEncoder.class, "encode", String.class, String.class)
.because("java.net.URLEncoder.encode(String s, Charset charset) should be used instead");
}
private ArchRule noClassesShouldCallURLDecoderWithStringEncoding() {
return ArchRuleDefinition.noClasses()
.should()
.callMethod(URLDecoder.class, "decode", String.class, String.class)
.because("java.net.URLDecoder.decode(String s, Charset charset) should be used instead");
}
public void setClasses(FileCollection classes) { public void setClasses(FileCollection classes) {
this.classes = classes; this.classes = classes;
} }

View File

@ -23,6 +23,7 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -159,7 +160,7 @@ final class ChangeableUrls implements Iterable<URL> {
urls.add(referenced); urls.add(referenced);
} }
else { else {
referenced = new URL(jarUrl, URLDecoder.decode(entry, "UTF-8")); referenced = new URL(jarUrl, URLDecoder.decode(entry, StandardCharsets.UTF_8));
if (new File(referenced.getFile()).exists()) { if (new File(referenced.getFile()).exists()) {
urls.add(referenced); urls.add(referenced);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import java.io.File;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContext;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -80,7 +81,7 @@ class SpringBootMockServletContextTests implements ServletContextAware {
}; };
URL resource = context.getResource("/"); URL resource = context.getResource("/");
assertThat(resource).isNotNull(); assertThat(resource).isNotNull();
File file = new File(URLDecoder.decode(resource.getPath(), "UTF-8")); File file = new File(URLDecoder.decode(resource.getPath(), StandardCharsets.UTF_8));
assertThat(file).exists().isDirectory(); assertThat(file).exists().isDirectory();
String[] contents = file.list((dir, name) -> !(".".equals(name) || "..".equals(name))); String[] contents = file.list((dir, name) -> !(".".equals(name) || "..".equals(name)));
assertThat(contents).isNotNull(); assertThat(contents).isNotNull();

View File

@ -20,12 +20,12 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -225,9 +225,9 @@ public class PropertiesLauncher extends Launcher {
return getFileResource(config); return getFileResource(config);
} }
private String handleUrl(String path) throws UnsupportedEncodingException { private String handleUrl(String path) {
if (path.startsWith("jar:file:") || path.startsWith("file:")) { if (path.startsWith("jar:file:") || path.startsWith("file:")) {
path = URLDecoder.decode(path, "UTF-8"); path = URLDecoder.decode(path, StandardCharsets.UTF_8);
if (path.startsWith("file:")) { if (path.startsWith("file:")) {
path = path.substring("file:".length()); path = path.substring("file:".length());
if (path.startsWith("//")) { if (path.startsWith("//")) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,12 +20,12 @@ import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import java.nio.charset.StandardCharsets;
import java.security.Permission; import java.security.Permission;
/** /**
@ -318,14 +318,9 @@ final class JarURLConnection extends java.net.JarURLConnection {
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
int c = source.charAt(i); int c = source.charAt(i);
if (c > 127) { if (c > 127) {
try { String encoded = URLEncoder.encode(String.valueOf((char) c), StandardCharsets.UTF_8);
String encoded = URLEncoder.encode(String.valueOf((char) c), "UTF-8");
write(encoded, outputStream); write(encoded, outputStream);
} }
catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex);
}
}
else { else {
if (c == '%') { if (c == '%') {
if ((i + 2) >= length) { if ((i + 2) >= length) {

View File

@ -20,6 +20,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -553,7 +554,7 @@ public class UndertowServletWebServerFactory extends AbstractServletWebServerFac
private URLResource getMetaInfResource(URL resourceJar, String path) { private URLResource getMetaInfResource(URL resourceJar, String path) {
try { try {
String urlPath = URLEncoder.encode(ENCODED_SLASH.matcher(path).replaceAll("/"), "UTF-8"); String urlPath = URLEncoder.encode(ENCODED_SLASH.matcher(path).replaceAll("/"), StandardCharsets.UTF_8);
URL resourceUrl = new URL(resourceJar + "META-INF/resources" + urlPath); URL resourceUrl = new URL(resourceJar + "META-INF/resources" + urlPath);
URLResource resource = new URLResource(resourceUrl, path); URLResource resource = new URLResource(resourceUrl, path);
if (resource.getContentLength() < 0) { if (resource.getContentLength() < 0) {