Tomcat HttpServletRequest.getParameter自动URL解码分析

1.  问题描述

在使用HttpServletRequest.getParameter方法获取HTTP请求参数时,发现经过URL编码的参数被自动进行了解码,与预期不一致。

2.  问题分析

2.1  调试

使用IDEA对程序进行调试,对HttpServletRequest.getParameter方法设置断点,发送HTTP请求,进入断点后,使用“Step Info (F5)”,调用堆栈如下:

可以看到对应org.apache.catalina.connector.RequestFacade类的getParameter方法。

2.2  添加依赖包

IDEA默认无法找到org.apache.catalina.connector.RequestFacade类,根据包名,可知以上类应在Tomcat的jar包中。

将Tomcat的lib目录添加至当前工程的依赖目录中后,IDEA可以找到org.apache.catalina.connector.RequestFacade类。

2.3  添加依赖包后调试

2.3.1  请求参数解析

添加依赖包后,继续调试,进入断点后,使用“Step Info (F5)”,进入RequestFacade类的getParameter方法,代码如下:

public String getParameter(String name) {
    
if (this.request == null) {
        
throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
    } 
else {
        
return Globals.IS_SECURITY_ENABLED ? (String)AccessController.doPrivileged(new RequestFacade.GetParameterPrivilegedAction(name)) : this.request.getParameter(name);
    }
}

org.apache.catalina.Globals类的IS_SECURITY_ENABLED变量值为“System.getSecurityManager() != null”,在当前条件下即false。

RequestFacade类的getParameter方法中的this.request.getParameter方法,对应org.apache.catalina.connector.Request类的getParameter方法,代码如下:

public String getParameter(String name) {

    if (!this.parametersParsed) {

        this.parseParameters();

    }

  

    return this.coyoteRequest.getParameters().getParameter(name);

}

在应用处理某次HTTP请求时,初始时parametersParsed参数为false(分析略),因此会执行parseParameters方法。

在Request类的parseParameters方法中,首先将this.parametersParsed设置为true(因此parseParameters方法在处理一次请求时只会执行一次解析请求参数的操作),后续调用parameters.processParameters(byte[] bytes, int start, int len)方法,对应org.apache.tomcat.util.http.Parameters类。

在Parameters类的processParameters(byte[] bytes, int start, int len)方法中,调用了processParameters(byte[] bytes, int start, int len, Charset charset)方法。

在processParameters(byte[] bytes, int start, int len, Charset charset)方法中,在while(pos < end)循环中,定义了parsingName、decodeName、decodeValue变量,分别用于标记当前在解析name还是value,当前是否需要对name进行解码,当前是否需要对value进行解码。

在while(pos < end)循环中,包含了do while循环,在其中的switch代码块中,对当前字节进行判断,若当前字符为“%+&=”等特殊字符时,对parsingName、decodeName、decodeValue变量值进行修改。可以看到当name或value中出现’%’或’+’时,会将需要解码的标志设置为真。相关代码如下所示:

switch(bytes[pos]) {

  case 37:

  case 43:

    if (parsingName) {

        decodeName = true;

    } else {

        decodeValue = true;

    }

  

    ++pos;

    break;

  case 38:

    if (parsingName) {

        nameEnd = pos;

    } else {

        valueEnd = pos;

    }

  

    parameterComplete = true;

    ++pos;

    break;

  case 61:

    if (parsingName) {

        nameEnd = pos;

        parsingName = false;

        ++pos;

        valueStart = pos;

    } else {

        ++pos;

    }

    break;

  default:

    ++pos;

}

以上涉及的十进制数如下

十进制数

十六进制数

字符

37

0x25

'%'

43

0x2B

'+'

38

0x26

'&'

61

0x3D

'='

在后续代码中,当decodeName变量为真时,会调用this.urlDecode(this.tmpName)方法;当decodeValue变量为真时,会调用this.urlDecode(this.tmpValue)方法。在urlDecode方法中,会进行URL解码。

在后续代码中,定义了“String name = this.tmpName.toString();”与“String value”(值为“this.tmpValue.toString()”或“""”)变量,分别对应当前解析的HTTP请求参数的name与value。并调用了this.addParameter(name, value);方法

在Parameters类的addParameter方法中,调用了this.paramHashValues.put(key, values);方法,将解析的请求参数保存至Map<String, ArrayList<String>> paramHashValues变量中。

2.3.2  请求参数获取

在Request类的getParameter方法中,当parametersParsed为假,调用完parseParameters方法之后,会调用this.coyoteRequest.getParameters().getParameter(name)方法,对应Parameters类的getParameter方法。

在Parameters类的getParameter方法中,从paramHashValues变量中获取请求参数值并返回,如下所示:

public String getParameter(String name) {

    this.handleQueryParameters();

    ArrayList<String> values = (ArrayList)this.paramHashValues.get(name);

    if (values != null) {

        return values.size() == 0 ? "" : (String)values.get(0);

    } else {

        return null;

    }

}

 

发布了37 篇原创文章 · 获赞 0 · 访问量 2333

猜你喜欢

转载自blog.csdn.net/a82514921/article/details/104579067