This is not a duplicate referenced question, because it is Spring specific. Whoever added that (3 years after the fact!) didn't bother to read the question or comment thread to see what the real answer was. The accepted answer isn't quite the answer, but the author of the answer never came back and edited it like I asked.
Given the restful method below, Spring 3.1 gives a 400 error with "The request sent by the client was syntactically incorrect ()." when the token parameter contains a URL encoded slash (%2F), for example "https://somewhere.com/ws/stuff/lookup/resourceId/287559/token/R4o6lI%2FbBx43/userName/jim" Without the %2F everything works fine. A 3rd party is already calling this service (of course!) so I can't change what they send, in the short term at least. Any ideas on how to work around this on the server side?
This problem is described very well here https://jira.springsource.org/browse/SPR-8662 though that issue is related to UriTemplate which I am not using that I can tell.
#RequestMapping("/ws/stuff/**")
#Controller
public class StuffController {
#RequestMapping(value = "/ws/stuff/lookup/resourceId/{resourceId}/token/{token}/userName/{userName}", method = RequestMethod.GET)
public #ResponseBody
String provisionResource(#PathVariable("resourceId") String resourceId, #PathVariable("token") String token, #PathVariable("userName") String userName, ModelMap modelMap,
HttpServletRequest request, HttpServletResponse response) {
return handle(resourceId, userName, request, token, modelMap);
}
}
Note: This is on Glassfish 3.1.2, and at first it was Grizzly/Glassfish not accepting the slash, but
-Dcom.sun.grizzly.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
fixed that.
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-2.http.encoded-slash-enabled=true
didn't seem to help.
for spring-boot, the following did the trick
#SpringBootApplication
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
SpringApplication.run(Application.class, args);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
This could be your answer: urlencoded Forward slash is breaking URL
I would suggest not putting that in the path, move it to a request param instead.
Work around:
You could change the RequestMapping to
#RequestMapping(value = "/ws/stuff/lookup/resourceId/**", method = RequestMethod.GET)
and then parse the path variables manually from the request object.
2019 Update for Spring Boot 2+ / Spring (Security) 5+ / Java 8+:
As my edit to iamiddy's answer was rejected I want to also provide the complete solution for Spring Boot 2 + as an separate answer.
The WebMvcConfigurerAdapter is deprecated with Spring5 / Java8 and can be replaced directly with the Interface WebMvcConfigurer ending up with:
#SpringBootApplication
public class Application extends WebMvcConfigurer {
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
SpringApplication.run(Application.class, args);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
Plus you also need to configure Spring's (Strict)HttpFirewall to avoid the blocking of encoded slashes with the error message The request was rejected because the URL contained a potentially malicious String "%2F"
#Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
return firewall;
}
Spring Boot will use the above HttpFirewall Bean when available - otherwise it might be necessary to configure the WebSecurity as mentioned here:
For spring boot application this worked for me..
Version 1
Add
org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
to your application.properties file
Version 2
run your spring boot application like this.
static void main(String[] args) {
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
SpringApplication.run this, args
}
Version 3 or run your java application with
-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
This fixed %2F encoded slash path variable for me.
Here is a fix for Spring 3.2.4 (should work for other versions as well). One must overwrite the default UrlPathHelper
public class UrlPathHelperFixed extends UrlPathHelper {
public UrlPathHelperFixed() {
super.setUrlDecode(false);
}
#Override
public void setUrlDecode(boolean urlDecode) {
if (urlDecode) {
throw new IllegalArgumentException("Handler [" + UrlPathHelperFixed.class.getName() + "] does not support URL decoding.");
}
}
#Override
public String getServletPath(HttpServletRequest request) {
return getOriginatingServletPath(request);
}
#Override
public String getOriginatingServletPath(HttpServletRequest request) {
return request.getRequestURI().substring(request.getContextPath().length());
}
}
And inject it to the Mapping Handler:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="order" value="-1"></property>
<property name="urlPathHelper">
<bean class="com.yoochoose.frontend.spring.UrlPathHelperFixed"/>
</property>
</bean>
After a day of hard works it works now for me :-)
It was suggested to Spring team as https://jira.springsource.org/browse/SPR-11101
I have found this solution which is working for me;
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
just before
springApplication.run(args);
and add below code in Application class
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false);
configurer.setUrlPathHelper(urlPathHelper);
}
We just ran into this issue at my office, we did what was suggestion above from what Solubris said where you put it in a query param. The only additional requirement is that the data could have an '&' as well, which would mess up the query param. All we had to do is encode the text before it is sent in the URL and even '&' were filtered out.
Another answer would be to encode "/" twice, which would produce "%252F". In your mapped endpoint, Spring will decode it back to "%2F". All you need more is to decode it one more time using something like this:
URLDecoder.decode(encoded_URL, "UTF-8");
The following resolved the BACK_SLASH issue:
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
SpringApplication.run(Application.class, args);
}
But, same functionality could be done via application.yml.
org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH: true
This setting doesn't work. I did not find a way for that, and still looking at it.
In order to avoid parsing the variables manually I did the following:
Add the following before executing any other code:
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
And in the controller, add 2 variables instead one, for example:
#RequestMapping(value = "/api/devices-by-name/device={deviceId}/restconf/data/ietf-interfaces:interfaces-state/interface={dpuIdPrefix}/{dpuIdSuffix}",
method = RequestMethod.GET,
produces = "application/json")
public ResponseEntity<String> getInterfaceState(#PathVariable(value = "deviceId") String deviceId,
#PathVariable(value = "dpuIdPrefix") String dpuIdPrefix,
#PathVariable(value = "dpuIdSuffix") String dpuIdSuffix) {
String dpuId = dpuIdPrefix + "/" + dpuIdSuffix;
And with that I can retrieve the following:
curl -s -X GET http://localhost:9090/api/devices-by-name/device=ZNSDX16DPU03/restconf/data/ietf-interfaces:interfaces-state/interface=gfast%200%2F14
If the slash is optional, then you might need to configure two different request mappings.
Related
I'm creating an endpoint using Spring Boot which executes a combination of system commands (java.lang.Runtime API) to generate a zip file to return to the client upon request, here's the code.
#GetMapping(value = "generateZipFile")
public ResponseEntity<Resource> generateZipFile(#RequestParam("id") Integer id) throws IOException {
org.springframework.core.io.Resource resource = null;
//generate zip file using commandline
resource = service.generateTmpResource(id);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/zip")
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"randomFile.zip\"")
.body(resource);
//somehow delete generated file here after client receives it
}
I cannot keep stacking up the files on the server for obvious disk limit reasons, so I'm looking for a way to delete the files as soon as the client downloads them. Is there a solution in Spring Boot for this? I basically need to hook a callback that would do the cleanup after the user receives the resource.
I'm using Spring Boot 2.0.6
You can create a new thread but a best solution would be create a ThreadPoolExecutor in order to manage threads or also Scheduled annotation helps us.
new Thread() {
#Override
public void run() {
service.cleanup(id);
}
}.start();
UPDATED
A best answer, it would be using a Stack combine with Thread.
Here is the solution that I've done.
https://github.com/jjohxx/example-thread
I ended up using a HandlerInterceptorAdapter, afterCompletion was the callback I needed. The only challenge I had to deal with was passing through the id of the resource to cleanup, which I handled by adding a header in my controller method:
#GetMapping(value = "generateZipFile")
public ResponseEntity<Resource> genereateZipFile(#RequestParam("id") Integer id,
RedirectAttributes redirectAttributes) throws IOException {
org.springframework.core.io.Resource resource = myService.generateTmpResource(id);;
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, "application/zip")
.header(MyInterceptor.TMP_RESOURCE_ID_HEADER, id.toString())
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"someFile.zip\"")
.body(resource);
}
The interceptor code:
#Component
public class MyInterceptor extends HandlerInterceptorAdapter {
public static final String TMP_RESOURCE_ID_HEADER = "Tmp-ID";
#Autowired
private MyService myService;
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if(response == null || !response.containsHeader(TMP_RESOURCE_ID_HEADER)) return;
String tmpFileId = response.getHeader(TMP_RESOURCE_ID_HEADER);
myService.cleanup(tmpFileId);
}
}
For more information about interceptors see here.
I have a REST endpoint implemented with Spring MVC #RestController. Sometime, depends on input parameters in my controller I need to send http redirect on client.
Is it possible with Spring MVC #RestController and if so, could you please show an example ?
Add an HttpServletResponse parameter to your Handler Method then call response.sendRedirect("some-url");
Something like:
#RestController
public class FooController {
#RequestMapping("/foo")
void handleFoo(HttpServletResponse response) throws IOException {
response.sendRedirect("some-url");
}
}
To avoid any direct dependency on HttpServletRequest or HttpServletResponse I suggest a "pure Spring" implementation returning a ResponseEntity like this:
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(newUrl));
return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
If your method always returns a redirect, use ResponseEntity<Void>, otherwise whatever is returned normally as generic type.
Came across this question and was surprised that no-one mentioned RedirectView. I have just tested it, and you can solve this in a clean 100% spring way with:
#RestController
public class FooController {
#RequestMapping("/foo")
public RedirectView handleFoo() {
return new RedirectView("some-url");
}
}
redirect means http code 302, which means Found in springMVC.
Here is an util method, which could be placed in some kind of BaseController:
protected ResponseEntity found(HttpServletResponse response, String url) throws IOException { // 302, found, redirect,
response.sendRedirect(url);
return null;
}
But sometimes might want to return http code 301 instead, which means moved permanently.
In that case, here is the util method:
protected ResponseEntity movedPermanently(HttpServletResponse response, String url) { // 301, moved permanently,
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, url).build();
}
As the redirections are usually needed in a not-straightforward path, I think throwing an exception and handling it later is my favourite solution.
Using a ControllerAdvice
#ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
NotLoggedInException.class
})
protected ResponseEntity<Object> handleNotLoggedIn(
final NotLoggedInException ex, final WebRequest request
) {
final String bodyOfResponse = ex.getMessage();
final HttpHeaders headers = new HttpHeaders();
headers.add("Location", ex.getRedirectUri());
return handleExceptionInternal(
ex, bodyOfResponse,
headers, HttpStatus.FOUND, request
);
}
}
The exception class in my case:
#Getter
public class NotLoggedInException extends RuntimeException {
private static final long serialVersionUID = -4900004519786666447L;
String redirectUri;
public NotLoggedInException(final String message, final String uri) {
super(message);
redirectUri = uri;
}
}
And I trigger it like this:
if (null == remoteUser)
throw new NotLoggedInException("please log in", LOGIN_URL);
if you #RestController returns an String you can use something like this
return "redirect:/other/controller/";
and this kind of redirect is only for GET request, if you want to use other type of request use HttpServletResponse
I am having a #RequestBody annotated argument in my method like this:
#RequestMapping(value = "/courses/{courseId}/{name}/comment", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public #ResponseBody CommentContainer addComment(#PathVariable Long courseId,
#ActiveAccount Account currentUser,
#Valid #RequestBody AddCommentForm form,
BindingResult formBinding,
HttpServletRequest request) throws RequestValidationException {
.....
}
Then I have a #InitBinder annotated method in the same controller:
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(AddCommentForm.class, new StringEscapeEditor());
}
My StringEscapeEditor is not running. But my initBinder method is. So it does not mapping my form to the escape editor. This seems right after reading this thread (Where it seems like #RequestMapping is not supported by #InitBinder):
spring mvc #InitBinder is not called when processing ajax request
And i tested to map a #PathVariable string and then my editor is working.
This is a big deal in my application since most of my bindings is done with #RequestBody and it would be great if i could apply some custom bindings to it.
What is the most common way to solve this problem? and to escape my input data for script attacks.
To escape XSS I suggest that escaping is done while outputting the data, because correct escaping depends on the output document.
If JSON response generated by #ResponseBody is consumed directly by the client and there is no opportunity to XSS escape the content, then JacksonMessageConverter can be customised to perform XSS escaping on strings.
One can customise JacksonMessageConverter like this:
1) First we create ObjectMapper factory that will create our custom object mapper:
public class HtmlEscapingObjectMapperFactory implements FactoryBean<ObjectMapper> {
private final ObjectMapper objectMapper;
public HtmlEscapingObjectMapperFactory() {
objectMapper = new ObjectMapper();
objectMapper.getJsonFactory().setCharacterEscapes(new HTMLCharacterEscapes());
}
#Override
public ObjectMapper getObject() throws Exception {
return objectMapper;
}
#Override
public Class<?> getObjectType() {
return ObjectMapper.class;
}
#Override
public boolean isSingleton() {
return true;
}
public static class HTMLCharacterEscapes extends CharacterEscapes {
private final int[] asciiEscapes;
public HTMLCharacterEscapes() {
// start with set of characters known to require escaping (double-quote, backslash etc)
asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
// and force escaping of a few others:
asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
}
#Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
// and this for others; we don't need anything special here
#Override
public SerializableString getEscapeSequence(int ch) {
return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));
}
}
}
(inspiration for HtmlCharacterEscapes came from this question: HTML escape with Spring MVC and Jackson Mapper)
2) Then we register the message converter that uses our custom object mapper (example in xml config):
<bean id="htmlEscapingObjectMapper" class="com.example.HtmlEscapingObjectMapperFactory" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" p:objectMapper-ref="htmlEscapingObjectMapper" />
</mvc:message-converters>
</mvc:annotation-driven>
Now all the JSON messages created by #ResponseBody should have strings escaped as specified in HTMLCharacterEscapes.
Alternative solutions to the problem:
XSS escape what you need in the controller body after the objects have been deserialised
maybe XSS escape in javascript on the client before outputting the content
In addition to doing output escaping, it may be useful to also do some input validation (using standard Spring validation methods) to block some of the content that you don't want to be entered into the system / database.
EDIT: JavaConfig
I haven't tried this out but in Java config it should work like this (you won't need Factory Bean from above because you can set up everything in config in this case):
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(buildHtmlEscapingJsonConverter());
}
private MappingJacksonHttpMessageConverter buildHtmlEscapingJsonConverter() {
MappingJacksonHttpMessageConverter htmlEscapingConverter = new MappingJacksonHttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.getJsonFactory().setCharacterEscapes(new HTMLCharacterEscapes());
htmlEscapingConverter.setObjectMapper(objectMapper);
return htmlEscapingConverter;
}
Please be aware that any other non-json default message converters that would normally be configured will now be lost (e.g. XML converters etc..) and if you need them, you will need to add them manually (you can see what's active by default here in section 2.2: http://www.baeldung.com/spring-httpmessageconverter-rest)
I have two Spring MVC controller methods. Both receive the same data in the request body (in the format of an HTLM POST form: version=3&name=product1&id=2), but one method handles PUT requests and another DELETE:
#RequestMapping(value = "ajax/products/{id}", method = RequestMethod.PUT)
#ResponseBody
public MyResponse updateProduct(Product product, #PathVariable("id") int productId) {
//...
}
#RequestMapping(value = "ajax/products/{id}", method = RequestMethod.DELETE)
#ResponseBody
public MyResponse updateProduct(Product product, #PathVariable("id") int productId) {
//...
}
In the first method, all fields of the product argument are correctly initialised. In the second, only the id field is initialised. Other fields are null or 0. (id is, probably, initialised because of the id path variable).
I can see that the HttpServletRequest object contains values for all fields in the request body (version=3&name=product1&id=2). They just are not mapped to the fields of the product parameter.
How can I make the second method work?
I also tried to use the #RequestParam annotated parameters. In the method that handles PUT requests, it works. In the DELETE method, I get an exception: org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'version' is not present.
I need to pass data in the body of DELETE requests because the data contain a row version which is used for optimistic locking.
The problem is not a Spring problem, but a Tomcat problem.
By default, Tomcat will only parse arguments that are in the form style, when the HTTP method is POST (at least for version 7.0.54 that I checked but it's probably the same for all Tomcat 7 versions).
In order to be able to handle DELETE methods as well you need to set the parseBodyMethods attribute of the Tomcat Connector. The connector configuration is done in server.xml.
Your updated connector would most likely look like:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
parseBodyMethods="POST,PUT,DELETE"
URIEncoding="UTF-8" />
Here is documentation page for configuring Tomcat connectors.
Once you setup Tomcat to parse the parameters, Spring will work just fine (although in your case you will probably need to remove #RequestBody from the controller method)
You can try adding the annotation #RequestBody to your Product argument.
But if you just need to pass version information, using a request param is more appropriate.
So add a new argument in your delete method #RequestParam("version") int version, and when calling the delete method pass a query param like ..ajax/products/123?version=1
As you said request param is not working for you in delete, can you post the exact url you used and the method signature ?
Spring boot 1.5.*
#Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory(){
#Override
protected void customizeConnector(Connector connector) {
super.customizeConnector(connector);
connector.setParseBodyMethods("POST,PUT,DELETE");
}
};
}
Passing data in the body of a DELETE request
#Component
public class CustomiseTomcat implements WebServerFactoryCustomizer {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers( new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setParseBodyMethods("POST,PUT,DELETE");
}
});
}
}
for spring boot 2.0+ :
#Bean
public TomcatServletWebServerFactory containerFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected void customizeConnector(Connector connector) {
super.customizeConnector(connector);
connector.setParseBodyMethods("POST,PUT,DELETE");
}
};
}
Using Websphere Application Server + Wicket 1.6
I am having some issues with my mounted URLs.
When I invoke an url akin to: localhost:9080/danesCooking/pies/meat I see the following in the Chrome network tab:
localhost:9080/danesCooking/pies/meat status code 302
localhost:9080/danesCooking/meat?2 status code 404
So it seems the /pies/ portion gets lost. This behaviour does not occur when I deploy my application to Tomcat\JBoss\... .
Possibly relevant, pies itself is not a mounted page.
I've already looked through some of the wicket issues\forums and it seems most issues seem to be either resolved\conflicting answers\have to do with relative urls (fixed in 1.5.x).
Has anyone experienced this issue and still recalls how to resolve this?
Used WAS *Versions: 7 and 8.5* liberty.
This issue is actually outlined here;
https://issues.apache.org/jira/browse/WICKET-3258
My resolution to the issue in Wicket 6.9.1 was;
public class MyApplication extends WebApplication {
#Override
public Class<? extends WebPage> getHomePage() {
return MyHomePage.class;
}
/* *********************************************** */
// Resolve Websphere Relative URL "sendRedirect" Bug
#Override
protected WebResponse newWebResponse(WebRequest webRequest, HttpServletResponse httpServletResponse) {
return new FixedServletWebResponse((ServletWebRequest) webRequest, httpServletResponse);
}
/**
* Websphere incorrectly handles relative redirect pages when "HttpServletResponse.sendRedirect(url)" is called.
*
* This small fix ensures that Websphere is only ever provided with absolute URLs so that this issue never occurs.
*/
private static class FixedServletWebResponse extends ServletWebResponse {
private final ServletWebRequest webRequest;
protected FixedServletWebResponse(ServletWebRequest webRequest, HttpServletResponse httpServletResponse) {
super(webRequest, httpServletResponse);
this.webRequest = webRequest;
}
#Override
public String encodeRedirectURL(CharSequence url) {
Url relativeUrl = Url.parse(url);
return new UrlRenderer(webRequest).renderFullUrl(relativeUrl);
}
}
/* *********************************************** */
}
You can also solve that using anonymous class as described in the Configuring and Deploying Open Source with WebSphere Application Server Liberty Profile chapter 5.2.4. (Tested with WLP 8.5.5.3 and Wicket 6.8.0)
Override the following method in your WebApplication class:
#Override
protected WebResponse newWebResponse(final WebRequest webRequest, final
HttpServletResponse httpServletResponse)
{
return new ServletWebResponse((ServletWebRequest) webRequest,
httpServletResponse)
{
#Override
public String encodeRedirectURL(final CharSequence relativeURL)
{
return new UrlRenderer(webRequest).renderFullUrl(Url.parse(relativeURL));
}
};
}
UPDATE
Other solution, instead of code change, is to set the following property in the webcontainer (works in 8.5.5.3):
<webContainer com.ibm.ws.webcontainer.redirectwithpathinfo="true" />