From 49d65d5c4149513354113c6389f312a95bca2749 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 14 Sep 2020 22:15:44 +0200 Subject: [PATCH 1/4] Configurable filename encoding in MimeMessageHelper Closes gh-25755 --- .../mail/javamail/MimeMessageHelper.java | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java index b5e37e3698..c9d21201a6 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -159,8 +159,6 @@ public class MimeMessageHelper { private static final String HEADER_PRIORITY = "X-Priority"; - private static final String HEADER_CONTENT_ID = "Content-ID"; - private final MimeMessage mimeMessage; @@ -175,6 +173,8 @@ public class MimeMessageHelper { private FileTypeMap fileTypeMap; + private boolean encodeFilenames = true; + private boolean validateAddresses = false; @@ -464,7 +464,7 @@ public class MimeMessageHelper { * Set the Java Activation Framework {@code FileTypeMap} to use * for determining the content type of inline content and attachments * that get added to the message. - *

Default is the {@code FileTypeMap} that the underlying + *

The default is the {@code FileTypeMap} that the underlying * MimeMessage carries, if any, or the Activation Framework's default * {@code FileTypeMap} instance else. * @see #addInline @@ -480,18 +480,40 @@ public class MimeMessageHelper { /** * Return the {@code FileTypeMap} used by this MimeMessageHelper. + * @see #setFileTypeMap */ public FileTypeMap getFileTypeMap() { return this.fileTypeMap; } + /** + * Set whether to encode attachment filenames passed to this helper's + * {@code #addAttachment} methods. + *

The default is {@code true} for compatibility with older email clients; + * turn this to {@code false} for standard MIME behavior. On a related note, + * check out JavaMail's {@code mail.mime.encodefilename} system property. + * @since 5.2.9 + * @see #addAttachment(String, DataSource) + * @see MimeBodyPart#setFileName(String) + */ + public void setEncodeFilenames(boolean encodeFilenames) { + this.encodeFilenames = encodeFilenames; + } + + /** + * Return whether to encode attachment filenames passed to this helper's + * {@code #addAttachment} methods. + * @since 5.2.9 + * @see #setEncodeFilenames + */ + public boolean isEncodeFilenames() { + return this.encodeFilenames; + } + /** * Set whether to validate all addresses which get passed to this helper. - * Default is "false". - *

Note that this is by default just available for JavaMail >= 1.3. - * You can override the default {@code validateAddress method} for - * validation on older JavaMail versions (or for custom validation). + *

The default is {@code false}. * @see #validateAddress */ public void setValidateAddresses(boolean validateAddresses) { @@ -500,6 +522,7 @@ public class MimeMessageHelper { /** * Return whether this helper will validate all addresses passed to it. + * @see #setValidateAddresses */ public boolean isValidateAddresses() { return this.validateAddresses; @@ -508,10 +531,8 @@ public class MimeMessageHelper { /** * Validate the given mail address. * Called by all of MimeMessageHelper's address setters and adders. - *

Default implementation invokes {@code InternetAddress.validate()}, + *

The default implementation invokes {@link InternetAddress#validate()}, * provided that address validation is activated for the helper instance. - *

Note that this method will just work on JavaMail >= 1.3. You can override - * it for validation on older JavaMail versions or for custom validation. * @param address the address to validate * @throws AddressException if validation failed * @see #isValidateAddresses() @@ -525,7 +546,8 @@ public class MimeMessageHelper { /** * Validate all given mail addresses. - * Default implementation simply delegates to validateAddress for each address. + *

The default implementation simply delegates to {@link #validateAddress} + * for each address. * @param addresses the addresses to validate * @throws AddressException if validation failed * @see #validateAddress(InternetAddress) @@ -885,9 +907,7 @@ public class MimeMessageHelper { Assert.notNull(dataSource, "DataSource must not be null"); MimeBodyPart mimeBodyPart = new MimeBodyPart(); mimeBodyPart.setDisposition(MimeBodyPart.INLINE); - // We're using setHeader here to remain compatible with JavaMail 1.2, - // rather than JavaMail 1.3's setContentID. - mimeBodyPart.setHeader(HEADER_CONTENT_ID, "<" + contentId + ">"); + mimeBodyPart.setContentID("<" + contentId + ">"); mimeBodyPart.setDataHandler(new DataHandler(dataSource)); getMimeMultipart().addBodyPart(mimeBodyPart); } @@ -997,7 +1017,8 @@ public class MimeMessageHelper { try { MimeBodyPart mimeBodyPart = new MimeBodyPart(); mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT); - mimeBodyPart.setFileName(MimeUtility.encodeText(attachmentFilename)); + mimeBodyPart.setFileName(isEncodeFilenames() ? + MimeUtility.encodeText(attachmentFilename) : attachmentFilename); mimeBodyPart.setDataHandler(new DataHandler(dataSource)); getRootMimeMultipart().addBodyPart(mimeBodyPart); } From c2f6a98c907c01596f3a082dd0bc3dcf65fcb927 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 14 Sep 2020 22:16:12 +0200 Subject: [PATCH 2/4] Lenient handling of empty Content-Disposition filename Closes gh-25769 --- .../src/main/java/org/springframework/http/HttpHeaders.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index aaa65b5e44..37c8445b9b 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -861,7 +861,7 @@ public class HttpHeaders implements MultiValueMap, Serializable public void setContentDispositionFormData(String name, @Nullable String filename) { Assert.notNull(name, "Name must not be null"); ContentDisposition.Builder disposition = ContentDisposition.builder("form-data").name(name); - if (filename != null) { + if (StringUtils.hasText(filename)) { disposition.filename(filename); } setContentDisposition(disposition.build()); @@ -888,7 +888,7 @@ public class HttpHeaders implements MultiValueMap, Serializable */ public ContentDisposition getContentDisposition() { String contentDisposition = getFirst(CONTENT_DISPOSITION); - if (contentDisposition != null) { + if (StringUtils.hasText(contentDisposition)) { return ContentDisposition.parse(contentDisposition); } return ContentDisposition.empty(); From 3c84863271ff90869598f63e8e90202fe5b0cb2b Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 14 Sep 2020 22:18:30 +0200 Subject: [PATCH 3/4] Polishing --- .../core/codec/StringDecoder.java | 10 ++---- .../support/StringToBooleanConverter.java | 7 ++-- .../support/StringToUUIDConverter.java | 4 ++- .../http/ContentDisposition.java | 2 +- src/docs/asciidoc/core/core-aop-api.adoc | 34 +++++++++---------- src/docs/asciidoc/core/core-aop.adoc | 4 +-- src/docs/asciidoc/core/core-beans.adoc | 7 ++-- 7 files changed, 33 insertions(+), 35 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java index 212d23900c..c2bf9620e1 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java @@ -103,6 +103,7 @@ public final class StringDecoder extends AbstractDataBufferDecoder { return this.defaultCharset; } + @Override public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { return (elementType.resolve() == String.class && super.canDecode(elementType, mimeType)); @@ -167,7 +168,6 @@ public final class StringDecoder extends AbstractDataBufferDecoder { /** * Finds the first match and longest delimiter, {@link EndFrameBuffer} just after it. - * * @param dataBuffer the buffer to find delimiters in * @param matcher used to find the first delimiters * @return a flux of buffers, containing {@link EndFrameBuffer} after each delimiter that was @@ -221,7 +221,6 @@ public final class StringDecoder extends AbstractDataBufferDecoder { } DataBuffer result = dataBuffers.get(0).factory().join(dataBuffers); - if (stripDelimiter && matchingDelimiter != null) { result.writePosition(result.writePosition() - matchingDelimiter.length); } @@ -229,8 +228,6 @@ public final class StringDecoder extends AbstractDataBufferDecoder { } - - /** * Create a {@code StringDecoder} for {@code "text/plain"}. * @param stripDelimiter this flag is ignored @@ -293,8 +290,7 @@ public final class StringDecoder extends AbstractDataBufferDecoder { private static final DataBuffer BUFFER = new DefaultDataBufferFactory().wrap(new byte[0]); - private byte[] delimiter; - + private final byte[] delimiter; public EndFrameBuffer(byte[] delimiter) { super(BUFFER); @@ -304,7 +300,6 @@ public final class StringDecoder extends AbstractDataBufferDecoder { public byte[] delimiter() { return this.delimiter; } - } @@ -313,7 +308,6 @@ public final class StringDecoder extends AbstractDataBufferDecoder { @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final LimitedDataBufferList list; - LimitChecker(int maxInMemorySize) { this.list = new LimitedDataBufferList(maxInMemorySize); } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java index f4f4393218..a476de69cc 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2020 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. @@ -30,9 +30,9 @@ import org.springframework.core.convert.converter.Converter; */ final class StringToBooleanConverter implements Converter { - private static final Set trueValues = new HashSet<>(4); + private static final Set trueValues = new HashSet<>(8); - private static final Set falseValues = new HashSet<>(4); + private static final Set falseValues = new HashSet<>(8); static { trueValues.add("true"); @@ -46,6 +46,7 @@ final class StringToBooleanConverter implements Converter { falseValues.add("0"); } + @Override public Boolean convert(String source) { String value = source.trim(); diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java index 6a3d858569..cb63290f4e 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -19,6 +19,7 @@ package org.springframework.core.convert.support; import java.util.UUID; import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -31,6 +32,7 @@ import org.springframework.util.StringUtils; final class StringToUUIDConverter implements Converter { @Override + @Nullable public UUID convert(String source) { return (StringUtils.hasText(source) ? UUID.fromString(source.trim()) : null); } diff --git a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java index c87efa771c..f5bbc04360 100644 --- a/spring-web/src/main/java/org/springframework/http/ContentDisposition.java +++ b/spring-web/src/main/java/org/springframework/http/ContentDisposition.java @@ -556,7 +556,7 @@ public final class ContentDisposition { private static class BuilderImpl implements Builder { - private String type; + private final String type; @Nullable private String name; diff --git a/src/docs/asciidoc/core/core-aop-api.adoc b/src/docs/asciidoc/core/core-aop-api.adoc index 46516b36ac..cbfbab9e0b 100644 --- a/src/docs/asciidoc/core/core-aop-api.adoc +++ b/src/docs/asciidoc/core/core-aop-api.adoc @@ -103,13 +103,13 @@ The `MethodMatcher` interface is normally more important. The complete interface The `matches(Method, Class)` method is used to test whether this pointcut ever matches a given method on a target class. This evaluation can be performed when an AOP proxy is created to avoid the need for a test on every method invocation. If the -two-argument `matches` method returns `true` for a given method, and the `isRuntime()` method -for the MethodMatcher returns `true`, the three-argument matches method is invoked on -every method invocation. This lets a pointcut look at the arguments passed to the -method invocation immediately before the target advice starts. +two-argument `matches` method returns `true` for a given method, and the `isRuntime()` +method for the MethodMatcher returns `true`, the three-argument matches method is +invoked on every method invocation. This lets a pointcut look at the arguments passed +to the method invocation immediately before the target advice starts. -Most `MethodMatcher` implementations are static, meaning that their `isRuntime()` method returns `false`. -In this case, the three-argument `matches` method is never invoked. +Most `MethodMatcher` implementations are static, meaning that their `isRuntime()` method +returns `false`. In this case, the three-argument `matches` method is never invoked. TIP: If possible, try to make pointcuts static, allowing the AOP framework to cache the results of pointcut evaluation when an AOP proxy is created. @@ -145,20 +145,20 @@ See the <> for a discussion of supported AspectJ pointcut [[aop-api-pointcuts-impls]] === Convenience Pointcut Implementations -Spring provides several convenient pointcut implementations. You can use some of them directly. -Others are intended to be subclassed in application-specific pointcuts. +Spring provides several convenient pointcut implementations. You can use some of them +directly; others are intended to be subclassed in application-specific pointcuts. [[aop-api-pointcuts-static]] ==== Static Pointcuts -Static pointcuts are based on the method and the target class and cannot take into account the -method's arguments. Static pointcuts suffice -- and are best -- for most usages. -Spring can evaluate a static pointcut only once, when a method is first -invoked. After that, there is no need to evaluate the pointcut again with each method -invocation. +Static pointcuts are based on the method and the target class and cannot take into account +the method's arguments. Static pointcuts suffice -- and are best -- for most usages. +Spring can evaluate a static pointcut only once, when a method is first invoked. +After that, there is no need to evaluate the pointcut again with each method invocation. -The rest of this section describes some of the static pointcut implementations that are included with Spring. +The rest of this section describes some of the static pointcut implementations that are +included with Spring. [[aop-api-pointcuts-regex]] ===== Regular Expression Pointcuts @@ -168,9 +168,9 @@ frameworks besides Spring make this possible. `org.springframework.aop.support.JdkRegexpMethodPointcut` is a generic regular expression pointcut that uses the regular expression support in the JDK. -With the `JdkRegexpMethodPointcut` class, you can provide a list of pattern strings. If -any of these is a match, the pointcut evaluates to `true`. (So, the result is -effectively the union of these pointcuts.) +With the `JdkRegexpMethodPointcut` class, you can provide a list of pattern strings. +If any of these is a match, the pointcut evaluates to `true`. (As a consequence, +the resulting pointcut is effectively the union of the specified patterns.) The following example shows how to use `JdkRegexpMethodPointcut`: diff --git a/src/docs/asciidoc/core/core-aop.adoc b/src/docs/asciidoc/core/core-aop.adoc index f7daf85c76..ffc3be3577 100644 --- a/src/docs/asciidoc/core/core-aop.adoc +++ b/src/docs/asciidoc/core/core-aop.adoc @@ -1649,7 +1649,7 @@ join point, unless you specify otherwise, the order of execution is undefined. Y control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the `org.springframework.core.Ordered` interface in the aspect class or annotating it with the `@Order` annotation. Given two aspects, the -aspect returning the lower value from `Ordered.getValue()` (or the annotation value) has +aspect returning the lower value from `Ordered.getOrder()` (or the annotation value) has the higher precedence. [NOTE] @@ -2918,7 +2918,7 @@ an aspect weaving phase to your build script. If you have chosen to use Spring AOP, you have a choice of @AspectJ or XML style. There are various tradeoffs to consider. -The XML style may most familiar to existing Spring users, and it is backed by genuine +The XML style may be most familiar to existing Spring users, and it is backed by genuine POJOs. When using AOP as a tool to configure enterprise services, XML can be a good choice (a good test is whether you consider the pointcut expression to be a part of your configuration that you might want to change independently). With the XML style, it is diff --git a/src/docs/asciidoc/core/core-beans.adoc b/src/docs/asciidoc/core/core-beans.adoc index 47a99e4b18..630599998c 100644 --- a/src/docs/asciidoc/core/core-beans.adoc +++ b/src/docs/asciidoc/core/core-beans.adoc @@ -42,7 +42,7 @@ information on using the `BeanFactory` instead of the `ApplicationContext,` see In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is -instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a +instantiated, assembled, and managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container. @@ -2125,7 +2125,7 @@ startup, because it must satisfy the singleton's dependencies. The lazy-initiali is injected into a singleton bean elsewhere that is not lazy-initialized. You can also control lazy-initialization at the container level by using the -`default-lazy-init` attribute on the `` element, a the following example shows: +`default-lazy-init` attribute on the `` element, as the following example shows: [source,xml,indent=0,subs="verbatim,quotes"] ---- @@ -4429,7 +4429,8 @@ which these `BeanFactoryPostProcessor` instances run by setting the `order` prop However, you can only set this property if the `BeanFactoryPostProcessor` implements the `Ordered` interface. If you write your own `BeanFactoryPostProcessor`, you should consider implementing the `Ordered` interface, too. See the javadoc of the -{api-spring-framework}/beans/factory/config/BeanFactoryPostProcessor.html[`BeanFactoryPostProcessor`] and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces for more details. +{api-spring-framework}/beans/factory/config/BeanFactoryPostProcessor.html[`BeanFactoryPostProcessor`] +and {api-spring-framework}/core/Ordered.html[`Ordered`] interfaces for more details. [NOTE] ==== From 3ec4538981eaddf63a82b8181134fbc85e79c65a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 14 Sep 2020 22:19:25 +0200 Subject: [PATCH 4/4] Upgrade to Checkstyle 8.36.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7727127953..10b45e5fbf 100644 --- a/build.gradle +++ b/build.gradle @@ -325,7 +325,7 @@ configure([rootProject] + javaProjects) { project -> } checkstyle { - toolVersion = "8.36" + toolVersion = "8.36.1" configDir = rootProject.file("src/checkstyle") }