Spring boot exact comma next to the path variable - java

I have an spring boot app with 2.1.0.RELEASE version.
I have a path variable in my url like #GetMapping("/{type}/car") and I call my app with:
http://localhost:8080/BMW;AAAAAAAAAAAAA/car
But i get only BMW string. Spring skip exact comma and "AAAAAAAAAAAAA".
I apply a filter and I have a same expereince with it. I wanna get path variable like "BMW;AAAAAAAAAAAAA", but I dont get it.
The reason why I want to filter this kind of call out, because it is a security hole.

Try encoding first, the value will be this BMW%3BAAAAAAAAAAAAA, you can use UriUtils for encoding and decoding.

I find a possible solution:
#Configuration
public class SolutionConfig extends WebMvcConfigurationSupport {
#Override
protected PathMatchConfigurer getPathMatchConfigurer() {
PathMatchConfigurer pathMatchConfigurer = super.getPathMatchConfigurer();
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
pathMatchConfigurer.setUrlPathHelper(urlPathHelper);
return pathMatchConfigurer;
}
}
Source code link: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java
Key method name is "removeSemicolonContent".

Related

Spring REST - Is there a way to override the character Spring uses to separate a query parameter into a list of values?

I'm writing a REST API using Spring and have certain clients to the service that cannot or will not change how they call my service.
Normally when sending a query param with a list of values you would just comma delimit the parameter and Spring will do the rest
curl http://host.com/api/endpoint?listParam=1,2,3
And the controller
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<String> listParam){
// Here, listParam is populated with 1,2,3
}
Unfortunately my clients are going to be passing lists with the bar | delimiter and it simply isn't possible to get them to change that.
Example: curl http://host.com/api/endpoint?listParam=1%7C2%7C3%7C
I would still like to use Spring to break these calls out into lists so I don't have to clutter my code with manual String.split() calls.
What I've already tried:
I found the #InitBinder annotation and wrote the following
#InitBinder
public void initBinder(WebDataBinder dataBinder){
dataBinder.registerCustomEditor(String[].class, new StringArrayPropertyEditor("|"));
}
However, this code doesn't seem to ever be called (watching with breakpoints) and requests using the bar as the delimiter fail with a 400 BAD REQUEST.
Any suggestions would be much appreciated, thanks!
404 is coming due to URL encoding issue.
You need to encode | then it will work, but it will create another problem, params would not be split.
To work around this you need to create a custom conversion that can convert String to Collection. For the custom conversion, you can check the StringToCollectionConverter class. Once you have custom conversion then you can register that service, in any of the configuration classes add following function
#Autowired
void conversionService(GenericConversionService genericConversionService) {
genericConversionService.addConverter(myStringToCollectionConvert());
}
#Bean
public MyStringToCollectionConvert myStringToCollectionConvert() {
return new MyStringToCollectionConvert();
}
In this MyStringToCollectionConvert is class that will parse String and converts to a collection of Strings.
I've accepted Sonus21's answer since his suggestion allowed me to hunt down an example that worked, but my solution was not exactly his.
The class StringToCollectionConverter did in fact exist for me, but it wasn't accessible and I couldn't use it in any way. However, in looking at the interface it implemented (ConditionalGenericConverter) and searching for more examples with Spring converters I eventually settled on the following solution.
The listParam in my question actually refers to a set of Enum values. The first thing I did was rewrite my controller to actually use the Enum values instead of raw Integers.
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<EnumClass> listParam){
// ...
}
Next, I wrote a Spring Custom Converter (Baeldung Doc)
public class CustomStringToEnumClassListConverter implements Converter<String, List<EnumClass>> {
#Override
public List<EnumClass> convert(String str) {
return Stream.of(
str.split("\\|")) // Here is where we manually delimit the incoming string with bars instead of commas
.map(i -> EnumClass.intToValue(Integer.parseInt(i))) // intToValue is a method I wrote to get the actual Enum for a given int
.collect(Collectors.toList());
}
}
Finally, I wrote a Config Bean and registered this Custom Converter with Spring:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new CustomStringToEnumClassListConverter());
}
}
Once all of this was done, Spring automatically populated the listParam list with EnumClass objects.

Equal sign and semicolons in Spring path variable

I have rest controller with path like: "/abc/{variable}"
#GetMapping(produces = "application/json", value = "/abc/{variable}")
#ResponseStatus(HttpStatus.OK)
public List<String[]> controller(#PathVariable String variable) {
//code
}
I would like to pass string with multiple semicolons and equal sign with request like :
{host}/abc/xyz=123;xyz
and get full string xyz=123;xyz; in path variable of controller method.
I have configured Spring to not remove semicolons like this:
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
But the result is truncated to xyz=123 in this example. (possibly because of how matrix variables work in Spring).
Is it possible to use path variable with multiple semicolons and equal signs in Spring and capture complete string?
Since ; (as well as =) is a special character you have to encode it before sending the URL to the server. After encoding your URL turns to:
{host}/abc/xyz%3D123%3Bxyz
Spring provides utility class UriUtils for encoding/decoding operations. So if you compose URL on the server side you can use UriUtils.encodePath() method to make sure that all special characters within the path are encoded.

Assign string value in constructor

Task:
I am trying to search solution for springboot application, where we would like to load CSV files, only in debug mode or development mode. Those CSV files contain test values, which represents data in correct format.
Solution:
According to spring boot documentation I choose the yaml configuration file, where I can say, that CSV file will be on some exact location (i.e.: classpath:/ ). Property looks like this:
spring
profiles: development
csv:
first-csv: classpath:/first.csv
Then there is logic when we are reading sources. This logic will decide if we will call production data or mock data (csv files) according to mode (development or any other). In development mode we will try to read csv files, which should be placed in root folder for an application (as you can see above in yaml configuration.
Problem:
When I call constructor for class, where I need to know the position of .csv files, I don't know how to set those string values to local string values through constructor. My call looks like this:
#Configuration
class WhereWeDecideWhichWayToGo() {
private final CSVProperties csvProperties;
//Code for production solution - which works
#Bean
#Profile("development")
public CreateMockData createMockData() {
return new MockData(csvProperties.getMockCSV());
}
}
Where CSVProperties is a class with only getters / setters for property from yaml configuration file. This class has annotation:
#Component
#ConfigurationProperties("csv")
And it works. The value from configuration file is read. During debug I can confirm, that csvProperties.getMockCSV() = "classpath:/first.csv", but its not assigned to proper variable in MockData class, which looks like this:
class MockData {
String CSV;
public MockData(
String mockCSV) {
// Following line is skipped in debug (and also in normal run)
CSV = mockCSV;
}
// Do some stuff with CSV file
}
Question:
Why is the line in constructor for MockData with initialisation of CSV String
CSV = mockCSV;
skipped - not assigned and the code just continue (skip assignment), even if mockCSV has a correct value:
classpath:/first.csv
I think, that the problem is initialisation order, because I am using the value of CSV in method after the constructor and its null.
As you seemed to ignore my suggestion to create an MCVE, I've attempted to do so using the code from your question, and it works for me.
The code you've posted is obviously heavily edited from what you're actually doing, so I had to guess a few things, and there are also a few syntax errors in the examples you've posted, so I've attempted to infer what they really are in your code. Here's what I've done that works, along with notes about where I've guessed, hopefully it will point you in the right direction.
So, from the beginning, here's the class with #SpringBootApplication, presumably you have something like this somewhere in your codebase:
#SpringBootApplication
public class CSVExampleApplication {
public static void main(String[] args) {
SpringApplication.run(CSVExampleApplication.class, args);
}
}
Here's the configuration properties class. Note that you don't need it to be an #Component.
#ConfigurationProperties("csv")
public class CSVProperties {
private String firstCsv;
public String getFirstCsv() {
return firstCsv;
}
public void setFirstCsv(String firstCsv) {
this.firstCsv = firstCsv;
}
}
I called the property firstCsv to match the value from your application.yml file which is first-csv. It's interesting that in your code you seem to be referring to this property with csvProperties.getMockCSV(). I'm guessing that's just a typo in your question, because that will never work - the property name needs to match the key in your application.yml file.
Here's the configuration class. You haven't shown this anywhere in your examples, but presumably you have an equivalent somewhere. The important part is the #EnableConfigurationProperties, which must specify the class that has the #ConfigurationProperties annotation.
#Configuration
#EnableConfigurationProperties(CSVProperties.class)
public class CSVExampleConfiguration {
private final CSVProperties csvProperties;
#Autowired
public CSVExampleConfiguration(CSVProperties csvProperties) {
this.csvProperties = csvProperties;
}
#Bean
#Profile("!development")
public MockData createProductionMockData() {
return new MockData("production");
}
#Bean
#Profile("development")
public MockData createMockData() {
return new MockData(csvProperties.getFirstCsv());
}
}
It's also interesting that in your question you say you're creating the bean with a method that looks like public CreateMockData createMockData() {, but then you return a MockData. Again, I'm guessing this is a typo in your question, as it won't compile like this (unless MockData extends CreateMockData, but that seems odd).
I'm not sure how you're setting the production profile, but hopefully what I've done above is a reasonable equivalent - #Profile("!development") says "create this bean if the 'development' profile is not set"
My application.yml file is also slightly different from what you've posted in your question:
spring:
profiles: development
csv:
first-csv: classpath:/first.csv
Your example won't work at all, firstly because you're missing a : after spring, and secondly because you seem to have csv nested under spring, which isn't how you set the configuration properties - it needs to be at the root level as above. Again, I assume these are just typos in the question because you say the application starts (which it wouldn't with the missing :) and that you see the configuration property get set (which it wouldn't with csv nested under spring).
Finally, I tested all this with a simple controller. You haven't given any details about the rest of your application, but this is what I've done, which hopefully mimics your code:
#RestController
public class TestController {
private final MockData mockData;
#Autowired
public TestController(MockData mockData) {
this.mockData = mockData;
}
#RequestMapping(value = "/foo", method = RequestMethod.GET)
public String getCsv() {
return mockData.getCSV();
}
}
So, then, with the application running and no profile set, if I visit http://localhost:8080/foo in a browser, I get the String "production" in the response, and if I restart the application, with a profile of "development", I get a response of "classpath:/first.csv".
I appreciate that this probably isn't the "just do this" answer that you might have hoped for, but I'd suggest you compare this to your code, and try to modify anything that differs. If you still can't get it to work, then there must be some other differences elsewhere in your application that are causing the problem. If you need more help, edit your question with the specifics that I've missed, but please try to post actual code (copy and pasted, not re-typed, to avoid introducing confusing typos).

java spring restful-url with semi-colon

I am currently working on a project that requires the use of a semi-colon in our restful-url scheme.
As you can imagine, we're having issues pulling this off - the underlying Java HTTPServletRequest isn't playing nicely.
Is there a way to work around this issue?
example restulf-URL:
http://service/BOB;MIKE/
Looks like Spring is only working on /service/bob - the ;MIKE gets truncated.
We've tried %3B (or %3F something like that) and it doesn't work.
Thanks in advance!
ct
While #Amos M. Carpenter answer is correct, here is the required configuration to turn this behavior off :
#Configuration
#EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter
{
#Override
public void configurePathMatch(PathMatchConfigurer configurer)
{
configurer.getUrlPathHelper().setRemoveSemicolonContent(false);
}
}
this is the list of all the delimiters you can legally use in a URI:
! $ & ( ) * , ; = + ' : # / ?
try each and see which works for you.
apparently Spring robs you of the ;. I'm not sure if that's a Servlet thing, but in any case, it sucks. A web framework should allow developers to construct ANY legal URIs.
Seems this is intentional on Spring's part, but at least it's configurable.
You need to tell your handler mapping to setRemoveSemicolonContent(false) (default is true). See the Javadoc for more details.
The reason why this was done is because they apparently wanted to remove the ;jsessionid=... suffix from URIs.

can we use spring expressions (spel) in other annotations?

I want to be able to do this:
#Controller
#RequestMapping("/#{handlerMappingPaths.security}/*")
public class SecurityController {
etc
//for instance, to resuse the value as a base for the folder resolution
#Value("#{handlerMappingPaths.security}/")
public String RESOURCE_FOLDER;
#RequestMapping(value="/signin-again", method = RequestMethod.GET)
public String signinAgainHandler() {
return RESOURCE_FOLDER + "signin_again";
}
}
this doesn't appear to work now, am I missing something?
One way you can find out things like this is to have a look yourself. This is an example for eclipse, but it should work similarly for other IDEs:
First of all, make sure you have the sources of the spring libraries you are using. This is easiest if you use maven, using the maven-eclipse-plugin or using m2eclipse.
Then, in Eclipse select Navigate -> Open Type.... Enter the type you are looking for (something like RequestMa* should do for lazy typers like myself). Enter / OK. Now right-click the class name in the source file and select References -> Project. In the search view, all uses of this class or annotation will appear.
One of them is DefaultAnnotationHandlerMapping.determineUrlsForHandlerMethods(Class, boolean), where this code snippet will tell you that expression language is not evaluated:
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
RequestMapping mapping = AnnotationUtils.findAnnotation(
method, RequestMapping.class);
if (mapping != null) {
String[] mappedPatterns = mapping.value();
if (mappedPatterns.length > 0) {
for (String mappedPattern : mappedPatterns) {
// this is where Expression Language would be parsed
// but it isn't, as you can see
if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) {
mappedPattern = "/" + mappedPattern;
}
addUrlsForPath(urls, mappedPattern);
}
}
else if (hasTypeLevelMapping) {
urls.add(null);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
Remember, it's called Open Source. There's no point in using Open Source Software if you don't try to understand what you are using.
Answering in 2020: with current Spring versions, SpEL expressions can be used in #RquestMappning annotations.
They are correctly parsed.
Inner details:
Spring's RequestMappingHandlerMapping calls embeddedValueResolver#resolveStringValue.
JavaDoc of EmbeddedValueResolver states the following:
StringValueResolver adapter for resolving placeholders and expressions
against a ConfigurableBeanFactory. Note that this adapter resolves
expressions as well, in contrast to the
ConfigurableBeanFactory.resolveEmbeddedValue method. The
BeanExpressionContext used is for the plain bean factory, with no
scope specified for any contextual objects to access.
Since: 4.3
This means both regular placeholders (e.g. ${my.property}) and SpEL expressions will be parsed.
Note that because regular placeholders are parsed first and SpEL expressions are parsed later, it's even possible to set the value of a property to a SpEL expression. Spring will first replace the placeholder with the property value (SpEL expression) and then parse the SpEL expression.
#Sean answered the question of whether spring supported this, but I also wanted to answer the question of just generally how not to duplicate configuration when using annotations. Turns out this is possible using static imports, as in:
import static com.test.util.RequestMappingConstants.SECURITY_CONTROLLER_PATH
#Controller
#RequestMapping("/" + SECURITY_CONTROLLER_PATH + "/*")
public class SecurityController {
etc
//for instance, to resuse the value as a base for the folder resolution
public String RESOURCE_FOLDER = SECURITY_CONTROLLER_PATH + "/";
#RequestMapping(value="/signin-again", method = RequestMethod.GET)
public String signinAgainHandler() {
return RESOURCE_FOLDER + "signin_again";
}
}

Categories