How to deal with special characters in @ PathVariable

  • 2021-08-28 20:16:49
  • OfStack

On the code:


 @GetMapping(value="/user/{useraccount}")
 public void getUserAccount(@PathVariable("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }

Normal access:

/user/zhangsan

Print: useraccount: zhangsan

It seems that 1 cut is normal

but:

Access:/user/zhangsan/lisi

Print: useraccount: zhangsan

Why not useraccount: zhangsan/lisi?

@ PathVariable is not as clever as we think, and the/in the parameter can't be separated from the actual path/

In fact, there are.; -You can't tell exactly when you wait.

What shall I do?

Two options:

1. Be simple and use @ RequestParam directly instead


 @GetMapping(value="/user")
 public void getUserAccount(@RequestParam("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }

With/user? useraccount = zhangsan Access

2. Use regular filtering


 @GetMapping(value="/user/{useraccount:[a-zA-Z0-9\\.\\-\\_\\;\\\]+}")
 public void getUserAccount(@PathVariable("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }

Normal access:

/user/zhangsan

Print: useraccount: zhangsan

Of course, this one is a bit inflexible, and the first one is simple and convenient

Supplement: Remember the troubleshooting problem that @ PathVariable special parameters will be lost once

If the request parameter contains., the parameter will be lost. Please see the following code

The following code, omitting the @ RestController control layer class code


@RequestMapping(value = "hello/{name}")
public Map<String, Object> sayHello(@PathVariable("name") String name, HttpServletRequest request) {
 Map<String, Object> rtnMap = new HashMap<>();
 rtnMap.put("msg", "hello " + name);
 return rtnMap;
}

Request address: hello/ddf, return {"msg": "hello ddf"}

Request address: hello/ddf. com, still return {"msg": "hello ddf"}

If you need to solve the above problem, you can change the code as follows (the solution is searched on the Internet)


@RequestMapping(value = "hello/{name:.*}")
public Map<String, Object> sayHello(@PathVariable("name") String name, HttpServletRequest request) {
 Map<String, Object> rtnMap = new HashMap<>();
 rtnMap.put("msg", "hello " + name);
 return rtnMap;
}

If you use @ PathVariable ending with special characters such as. sh or. bat, the actual returned data will be affected

The error is reported as follows:


{
 "timestamp": 1541405292119,
 "status": 406,
 "error": "Not Acceptable",
 "exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
 "message": "Could not find acceptable representation",
 "path": "/HDOrg/user/hello/ddf.sh"
}

Or the code above

The following code, omitting the @ RestController control layer class code


@RequestMapping(value = "hello/{name:.*}")
public Map<String, Object> sayHello(@PathVariable("name") String name, HttpServletRequest request) {
 Map<String, Object> rtnMap = new HashMap<>();
 rtnMap.put("msg", "hello " + name);
 return rtnMap;
}

If the request address at this time is hello/ddf. sh or hello/ddf. com. sh, as long as it ends with. sh, the business logic code will not be affected at this time, but when Spring's own code processes the returned data, there is a function that will determine the return type according to the extension, and the extension ending with. sh is sh, which will be resolved into the corresponding Content-Type: application/x-sh.

The solution is as follows. The first method is found on the Internet. It can be directly disabled, but it may affect the access to static resources. It is uncertain and has not been tried


@Configuration
public class Config extends WebMvcConfigurerAdapter {
 @Override
 public void configureContentNegotiation(
  ContentNegotiationConfigurer configurer) {
 configurer.favorPathExtension(false);
 }
}

Then the following is idle nothing really want to change ideas and try to see what is going on. Because of limited personal ability, the importance of the following contents is not guaranteed;

The second way to solve this problem is, Since there is a problem with extensions ending with. sh and so on, Then can you not let the program recognize the extension as. sh, Or simply skip the processing, For example, if I can add. sh/, this will affect the actual extension. But without affecting the existing code, Actually, there is a lazy writing here. You can directly add 1/at the end of value in @ RequestMapping. But this requires the client to finally spell 1/on the original conditions. Otherwise will not find the corresponding mapping, directly 404, I encountered this problem here, because the method has been online and several other system calls, so it will be a little cumbersome to change, so I sought a troublesome way, first put the solution in the following, not sure whether it will affect other problems

The solution of this way is as follows: The two lines of code in the comment can choose one from two, and the previous writing method is recommended, which has been skipped directly


@RequestMapping(value = "hello/{name:.*}")
public String sayHello(@PathVariable("name") String name) {
 //  This method skips determining by the way described above MediaType
 request.setAttribute(PathExtensionContentNegotiationStrategy.class.getName() + ".SKIP", true);
 //  The first half of the value of the following parameter must be the same as the RequestMapping1 To, otherwise invalid, excluding ContextPath
 request.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE, "/hello/" + name + "/");
 return "hello " + name;
}

Below depends on the source code to see 1 below why can do so, first see 1 below why can cause this result? The following steps focus only on the parts related to the current problem, and only roughly focus on the problems, without going deep into the details

After debug, you can see that the error is reported in the following process, first of all, as follows


public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
 @Override
 public void handleReturnValue(Object returnValue, MethodParameter returnType,
  ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
  throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
 mavContainer.setRequestHandled(true);
 ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
 // Try even with null return value. ResponseBodyAdvice could get involved.
 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
 }
}

Appears this question, 1 general search thought is whether is the request or the response Content-Type whether appears the question, then in the above this method whether inputMessage or outputMessage is normal, the emphasis looks at 1 under the writeWithMessageConverters () method, this method, part of the code is as follows


 @GetMapping(value="/user")
 public void getUserAccount(@RequestParam("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }
0

Look at the method getAcceptableMediaTypes () first, which determines what type of data the current HttpServletRequest wants to request according to the request. The method call chain is described later;

The getProducibleMediaTypes () method returns the MediaType that can be generated, which can be generated depending on how many MediaType can be supported in the current project 1. Of course, it can also be seen that the attribute HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE can be explicitly set by HttpServletRequest to determine which way to use;

After getting these two lists, It is necessary to judge whether requestedMediaTypes is compatible with producibleMediaTypes, For example, */* can be compatible with all MediaType that can be generated, and finally the compatible requestedMediaTypes will be cycled to see if it is a specific MediaType instead of a wildcard character, then the final effective MediaType is this. Of course, if there are many, there will be many that are not wildcard and meet the conditions. Therefore, before recycling, it is also sorted once to ensure that the one with the highest priority will take effect.


 @GetMapping(value="/user")
 public void getUserAccount(@RequestParam("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }
1

MediaType.java


 @GetMapping(value="/user")
 public void getUserAccount(@RequestParam("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }
2

The result of this method shows that if the called method returns an empty list, the method returns a list of MediaType. ALL, and its value can be seen from the code as */*. The code for calling this method is as follows:


public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
 
 @Override
 public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
 for (ContentNegotiationStrategy strategy : this.strategies) {
  List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
  if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
  continue;
  }
  return mediaTypes;
 }
 return Collections.emptyList();
 }
}

The call is as follows:


 @GetMapping(value="/user")
 public void getUserAccount(@RequestParam("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }
4

You can see here that there is one attribute skip, and if its attribute is PathExtensionContentNegotiationStrategy's full class name + ". SKP" and its value is true, then the null collection is returned without proceeding, and you have seen earlier that if the null collection is returned, it is actually returned to the caller */*, combined with what you saw earlier

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)

This method, */* can match any generated producibleMediaTypes, so the final result can be returned correctly according to the original type, and will not be affected by suffixes such as. sh;

In fact, when I didn't see skip at first, I saw some later codes, which finally solved this problem. Whether it is correct or not, I first recorded the whole process. If skip=true was not set in the above steps, then the continued part of the program went as follows

If uid ends with. sh, when the framework processes return data after the logical processing is completed, the returned content-type and sh ends will be determined according to the extension

Will affect the return content-type as application/x-sh, which will affect the actual functionality of the method. The solution is:

Either disable this feature or modify the @ RequestMapping of this method, disabling is not sure whether it will affect directly accessed static resources,

Moreover, the method caller project has been online, so it is not easy to modify it. Only the address of this attribute can be changed here, which affects the framework

This problem is avoided by getting the request suffix null later, but we can't confirm whether there will be other problems if requestUrl and mappingUrl are not 1


 @GetMapping(value="/user")
 public void getUserAccount(@RequestParam("useraccount") String userAccount) {
 
 logger.info("useraccount  : " + userAccount);
 }
5

Related articles: