JSON parse error: Illegal unquoted character ((CTRL-CHAR, code 13))

Viewed 111

Need help with parsing from json, i have a REST method that return pagination with sorting and filtering, but my job in name, that ends with \ like david\ not to throw any exceptions instead remove it \ in code. Below my UserController class:

@PutMapping
public PageDTO<UserDTO> find(@RequestBody PageRequest<UserFilter, UserSort> request) {
    return userService.find(request);
}

Below PageRequest and UserFilter


@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PageRequestDTO<UserFilter, UserSort> {

    private Integer size;
    private Integer page;
    private UserFilter filter;
    private UserSort sort;

}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserFilterDTO {
    
    private String name;
}

And my exception

2022-sep.-09 15:55:44.786 WARN  [http-nio-8083-exec-3] c.e.s.c.a.CommonControllerAdvice - Handling undefined exception: JSON parse error: Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backslash to be included in string value; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backslash to be included in string value
 at [Source: (PushbackInputStream); line: 6, column: 35] (through reference chain: com.example.spring.dto.PageRequest["filter"]->com.example.spring.dto.UserFilter["nameSearchText"])
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backslash to be included in string value; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backslash to be included in string value
 at [Source: (PushbackInputStream); line: 6, column: 35] (through reference chain: com.example.spring.dto.PageRequest["filter"]->com.example.spring.dto.UserFilter["nameSearchText"])
  at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:389)
  at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342)
  at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:185)
  at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:160)
  at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:133)
  at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
  at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)
  at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)
  at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
  at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
  at org.springframework.web.servlet.FrameworkServlet.doPut(FrameworkServlet.java:920)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:684)
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
  at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
  at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
  at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
  at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
  at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
  at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
  at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
  at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
  at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
  at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
  at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)
  at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
  at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
  at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
  at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backslash to be included in string value
 at [Source: (PushbackInputStream); line: 6, column: 35] (through reference chain: com.example.spring.dto.PageReques["filter"]->com.example.spring.dto.UserFilter["nameSearchText"])

com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:393)
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:378)
  ... 51 common frames omitted
Caused by: com.fasterxml.jackson.core.JsonParseException: Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backslash to be included in string value
 at [Source: (PushbackInputStream); line: 6, column: 35]

i tryied this property "spring:jackson:parser:allow-unquoted-control-chars: true" in my yml file, but instead of above exception i got this one

"JSON parse error: Unexpected character ('s' (code 115)): was expecting comma to separate Object entries; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected character ('s' (code 115)): was expecting comma to separate Object entries\n at [Source: (PushbackInputStream); line: 6, column: 50] (through reference chain: com.example.service.spring.dto.PageRequest[\"filter\"])"

JSON sample

{ "pageSize":5, "pageIndex":0, "filter": { "name": "user_2\" } }
3 Answers

In the

{ "pageSize":5, "pageIndex":0, "filter": { "name": "user_2\" } }

sample JSON the \" part means that you escape the quote, which will cause troubles when the JSON is being parsed. You need to receive a String where the \ character is escaped, as

{ "pageSize":5, "pageIndex":0, "filter": { "name": "user_2\\" } }

Now, it's quite possible that you have the JSON above somewhere, escaping the backslash, but this ends up with a single backslash on the receiving end. In that case you need to escape the escaped backslash (yeah, it sounds weird, but sometimes that's the case), as

{ "pageSize":5, "pageIndex":0, "filter": { "name": "user_2\\\\" } }

If you need further help, then you will also need to provide further information.

As Lajos' answer explains, the problem is that \" is inadvertently escaping the double quote. That causes it to not be treated as a string end. The rest of the JSON is then parsed as part of the quoted string until the parser gets to the end.

Now I surmise that the input text actually ends with a CR LF sequence. When the parser encounters the CR it says

Illegal unquoted character ((CTRL-CHAR, code 13))

because the ASCII code for CR is 13 decimal.

The error message seems to imply that the CR would be legal if it was quoted. In fact, no characters less than decimal 32 are permitted in JSON strings at all ... unless you express them as a JSON escape sequence.

The JSON string in your example is not a valid one: '{ "pageSize":5, "pageIndex":0, "filter": { "name": "user_2\" } }' You must return 400 bad request because of malformed request. The client is just not a proper REST client and instead of supporting bad requests fixing the client would be the real solution.

People spent a lot of time on writing a proper JSON parser and if you add a custom one for malformed JSONs, it will be a potential security issue. For example you fix the above JSON string by replacing \ with \\, then I add a string something like "user_2\"..." which will be changed to "user_2\\"...", which is invalid again. If this is filled with user input, then the user might be able to inject another property in the ... part of the message, which can be used to replace another property, which is not filled by the user. So it is a very bad idea to fix or write parsers without knowing what you are doing. Whoever gave you this job is completely incompetent from both programming and information security perspective.

Related