0

I have a use case where I need to verify if the incoming request body to my controller contains any special characters in a Hybris storefront. Though it can be achieved from the front-end by blocking any special characters, we require back-end validation.

I tried using HandlerIntercepterAdapter to intercept the request and validate for any special characters. But whenever I use request.getReader() or request.getInputStream() and read the data, request body is cleared. I tried using IOUtils.copy() but this too reads from the original request and makes the body empty. Even after wrapping the request with HttpServletRequestWrapper or ContentCachingRequestWrapper, the request body gets cleared. I guess internally somewhere it uses the same reference. I tried following this thread but was unable to solve this issue.

I am looking for a solution where I can extract the request body and validate it without letting it get cleared so it can be used in different controllers [or] any alternative approach which can help in preventing any special characters to hit the controller.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Himanshu Jain
  • 334
  • 2
  • 12

2 Answers2

1

any alternative approach which can help in preventing any special characters to hit the controller.

What if you try to do the following ...

  1. Get the request body
  2. Process it
  3. Set the request body again in your filter by setting the body to the processed version ?
@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
       
        HttpServletRequest originalRequest = (HttpServletRequest) request;
        HttpServletResponse originalResponse = (HttpServletResponse) response;

        /**
         * 2.Read the original request body and change it
         */
        String originalRequestBody = ServletUtil.readRequestBody(originalRequest); // Read the original request body
        // Body is processed here !
        String modifyRequestBody = processBody(originalRequestBody); // Modify request body (clear text)
        HttpServletRequest orginalRequest = (HttpServletRequest) request;
        ModifyRequestBodyWrapper requestWrapper = new ModifyRequestBodyWrapper(orginalRequest, modifyRequestBody);

        /**
         * 3. Build a new response object
         */
        ModifyResponseBodyWrapper responseWrapper = new ModifyResponseBodyWrapper(originalResponse);
        chain.doFilter(requestWrapper, responseWrapper);
        String originalResponseBody = responseWrapper.getResponseBody(); // Original response body (clear text)
        String modifyResponseBody = this.encryptBody(originalResponseBody); // Modified response volume (ciphertext)

        /**
         * 4.Output the modified response body with the output stream of the original response object
         * To ensure that the response type is consistent with the original request, and reset the response body size
         */
        originalResponse.setContentType(requestWrapper.getOrginalRequest().getContentType()); // Be consistent with the request
        byte[] responseData = modifyResponseBody.getBytes(responseWrapper.getCharacterEncoding()); // The coding is consistent with the actual response
        originalResponse.setContentLength(responseData.length);
        @Cleanup ServletOutputStream out = originalResponse.getOutputStream();
        out.write(responseData);
    }

Here is a code example, which implements this.

Arthur Klezovich
  • 2,595
  • 1
  • 13
  • 17
  • I tried implementing the above. But above code also uses getInputStream() internally making the request empty. Once the request is empty, we cannot use it with other controllers. Also, we cannot assign the generated string back to the request. – Himanshu Jain Nov 11 '21 at 12:54
  • I missed adding 1 line from above and it was not working. Thanks!! – Himanshu Jain Nov 28 '21 at 06:29
0

The input should be set inside a form.

In your controller, you can use a validator :

    @RequestMapping(value = "/process", method = RequestMethod.POST)
public String doValidateAndPost(final MyForm form, final BindingResult bindingResult,
        final HttpServletRequest request, final Model model){

        getMyValidator().validate(form, bindingResult);
        if (bindingResult.hasErrors())
        {
            return MY_PAGE;
        }

The validator will look like this :

@Override
public void validate(final Object object, final Errors errors)
{
    final MyForm form = (MyForm ) object;

    final String data = form.getMyData();
    
    Pattern p = Pattern.compile("[^a-z0-9 ]", Pattern.CASE_INSENSITIVE);

    Matcher m = p.matcher(data );

    boolean b = m.find();

    if (b)
    {
        errors.rejectValue("myData", "myData.invalid");
    }
}

You can also use the @Valid annotation :

public String doValidateAndPost(@Valid final MyForm form ...

And set in your form :

@Pattern(regexp = "[a-z0-9 ]")
private String myData;
alain.janinm
  • 19,951
  • 10
  • 65
  • 112