Skip to content

Add support for double backslashes to StringUtils#cleanPath #32962

Closed
@floringolintchi

Description

@floringolintchi

Affects: 6.1.8

While upgrading a project from spring boot 3.1.12 to spring boot 3.2.6 I've encountered issues with my @Value injection path to system file on my Windows machine being rebuilt incorrectly by StringUtils.cleanPath(...). I believe this concerns the way Spring handles \ characters when loading maven properties in application.yml. I've created a demo I'll attach showcasing the issue.

My file is loaded like this:

    @Value("${path.to.a.file}")
    private RSAPublicKey resource;

where the property in application.yml looks like this:

path.to.a.file: file:@path.to.a.key.file@

the maven property referenced is:

<properties>
        <main.basedir>${project.basedir}${file.separator}..</main.basedir>
        <path.to.a.key.file>${main.basedir}${file.separator}lib${file.separator}someFile</path.to.a.key.file>
</properties>

(the original project is a multi-module project)

From my testing this is related to the change to https://github.com/spring-projects/spring-framework/blob/v6.1.8/spring-core/src/main/java/org/springframework/util/ResourceUtils.java#L412
in previous major version this would build directly without any changes to location:
https://github.com/spring-projects/spring-framework/blob/v6.0.21/spring-core/src/main/java/org/springframework/util/ResourceUtils.java#L419

I don't think this is an issue with StringUtils.cleanPath(...), but with the fact that Spring will inject this maven property with double \\, rather than just one \. Example images from the demo project I have attached, how application.yml looks like in target folder:

Screenshot 2024-06-05 173850

And debugging, what the String is equal to since I was confused by the IntelliJ output:

Screenshot 2024-06-05 174205

how cleanPath outputs the wrong path when given input with double \:

Screenshot 2024-06-05 174536

after removing one escaped \, cleanPath works as expected:

Screenshot 2024-06-05 174644

From what I can tell maven won't use double backslashes so these are added by Spring I believe (output of maven command to check how properties look like):

Screenshot 2024-06-05 175306

Here is the stack trace:

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-06-05T18:05:06.429+03:00 ERROR 28320 --- [ main] o.s.boot.SpringApplication : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.Main$ExampleComponent': Unsatisfied dependency expressed through field 'resource': Failed to convert value of type 'java.lang.String' to required type 'java.security.interfaces.RSAPublicKey'; Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.security.interfaces.RSAPublicKey] for value [file:C:\Users\...\IdeaProjects\demo_resourceUtils_issue\child\..\lib\someFile]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:787) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:767) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:508) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1421) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962) ~[spring-context-6.1.8.jar:6.1.8]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.8.jar:6.1.8]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.6.jar:3.2.6]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.6.jar:3.2.6]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.6.jar:3.2.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.2.6.jar:3.2.6]
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:149) ~[spring-boot-3.2.6.jar:3.2.6]
at com.example.Main.main(Main.java:14) ~[classes/:na]
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.security.interfaces.RSAPublicKey'; Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.security.interfaces.RSAPublicKey] for value [file:C:\Users\...\IdeaProjects\demo_resourceUtils_issue\child\..\lib\someFile]
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:87) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:71) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1381) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:784) ~[spring-beans-6.1.8.jar:6.1.8]
... 19 common frames omitted
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.beans.factory.annotation.Value java.security.interfaces.RSAPublicKey] for value [file:C:\Users\...\IdeaProjects\demo_resourceUtils_issue\child\..\lib\someFile]
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-6.1.8.jar:6.1.8]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:182) ~[spring-core-6.1.8.jar:6.1.8]
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:131) ~[spring-beans-6.1.8.jar:6.1.8]
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:80) ~[spring-beans-6.1.8.jar:6.1.8]
... 23 common frames omitted
Caused by: java.io.UncheckedIOException: java.io.FileNotFoundException: C:\Users...\IdeaProjects\demo_resourceUtils_issue\child\lib\someFile (The system cannot find the path specified)
at org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor$ResourceKeyConverterAdapter.toInputStream(RsaKeyConversionServicePostProcessor.java:158) ~[spring-security-config-6.2.4.jar:6.2.4]
at org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor$ResourceKeyConverterAdapter.lambda$pemInputStreamConverter$0(RsaKeyConversionServicePostProcessor.java:146) ~[spring-security-config-6.2.4.jar:6.2.4]
at org.springframework.core.convert.converter.Converter.lambda$andThen$0(Converter.java:62) ~[spring-core-6.1.8.jar:6.1.8]
at org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor$ResourceKeyConverterAdapter.convert(RsaKeyConversionServicePostProcessor.java:132) ~[spring-security-config-6.2.4.jar:6.2.4]
at org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor$ResourceKeyConverterAdapter.convert(RsaKeyConversionServicePostProcessor.java:113) ~[spring-security-config-6.2.4.jar:6.2.4]
at org.springframework.core.convert.support.GenericConversionService$ConverterAdapter.convert(GenericConversionService.java:358) ~[spring-core-6.1.8.jar:6.1.8]
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-6.1.8.jar:6.1.8]
... 26 common frames omitted
Caused by: java.io.FileNotFoundException: C:\Users...\IdeaProjects\demo_resourceUtils_issue\child\lib\someFile (The system cannot find the path specified)
at java.base/java.io.FileInputStream.open0(Native Method) ~[na:na]
at java.base/java.io.FileInputStream.open(FileInputStream.java:216) ~[na:na]
at java.base/java.io.FileInputStream.(FileInputStream.java:157) ~[na:na]
at java.base/java.io.FileInputStream.(FileInputStream.java:111) ~[na:na]
at java.base/sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:86) ~[na:na]
at java.base/sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:189) ~[na:na]
at org.springframework.core.io.UrlResource.getInputStream(UrlResource.java:233) ~[spring-core-6.1.8.jar:6.1.8]
at org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor$ResourceKeyConverterAdapter.toInputStream(RsaKeyConversionServicePostProcessor.java:155) ~[spring-security-config-6.2.4.jar:6.2.4]
... 32 common frames omitted

This can easily be fixed by editing the string with SpEL: @Value("#{'${path.to.a.file}'.replace('\\\\', '\\')}") or just telling maven to add the files to my classpath, but I am opening this in case this is an issue worth considering, I apologize otherwise.

Demo project: demo_resourceUtils_issue.zip

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: regressionA bug that is also a regression

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions