一、现象
大家在java web 开发过程中,应该遇到过请求参数中有特殊字符导致后台获取参数时异常的情况。
参数中有 + : 比如参数是 voteless+cz ,在后台用 request.getParameter(); 获取时,发现变成了 voteless cz。就是 + 变成了 空格;
参数中有& : 比如参数是 voteless&cz,在后台用 request.getParameter(); 获取时,发现变成了 voteless。就是 &和之后的参数会丢失。
参数中有%: 比如参数是 voteless%cz,在后台用 request.getParameter(); 获取时,发现变成了 voteless�。%和后面的两个字符会变成乱码
参数中有%: 比如参数是 voteless%c,在后台用 request.getParameter(); 获取时,发现是null。获取不到
二、原因
原因是tomcat容器会丢对请求的参数(QueryString)进行类似 URLDecoder.decode的操作(GET和POST的请求都会,POST是 application/x-www-form-urlencoded )。
1、&问题
大家都知道&的目的是分隔 key=value。而value中有& 的话,其实很自然可以想到value中 & 后面的字符串会丢失,大家看如下的源码片段就明白了。
org.apache.tomcat.util.http.Parameters 的 processParameters(byte bytes[], int start, int len, Charset charset) 方法
while(pos < end) {
int nameStart = pos;
int nameEnd = -1;
int valueStart = -1;
int valueEnd = -1;
boolean parsingName = true;
boolean decodeName = false;
boolean decodeValue = false;
boolean parameterComplete = false;
do {
switch(bytes[pos]) {
case '=':
if (parsingName) {
// Name finished. Value starts from next character
nameEnd = pos;
parsingName = false;
valueStart = ++pos;
} else {
// Equals character in value
pos++;
}
break;
case '&':
if (parsingName) { // 重点在这个地方,value中&后面的字符串会当做是没有value的key
// Name finished. No value.
nameEnd = pos;
} else {
// Value finished
valueEnd = pos;
}
parameterComplete = true;
pos++;
break;
case '%':
case '+':
// Decoding required
if (parsingName) {
decodeName = true;
} else {
decodeValue = true;
}
pos ++;
break;
default:
pos ++;
break;
}
} while (!parameterComplete && pos < end);
if (pos == end) {
if (nameEnd == -1) {
nameEnd = pos;
} else if (valueStart > -1 && valueEnd == -1){
valueEnd = pos;
}
}
2、+和 % 问题
上面提到tomcat会对参数进行解码,解码部分的源码如下 org.apache.tomcat.util.buf.UDecoder 的 String convert(String str, boolean query)
while (strPos < strLen) {
int laPos; // lookahead position
// look ahead to next URLencoded metacharacter, if any
for (laPos = strPos; laPos < strLen; laPos++) {
char laChar = str.charAt(laPos);
if ((laChar == '+' && query) || (laChar == '%')) {
break;
}
}
// if there were non-metacharacters, copy them all as a block
if (laPos > strPos) {
dec.append(str.substring(strPos,laPos));
strPos = laPos;
}
// shortcut out of here if we're at the end of the string
if (strPos >= strLen) {
break;
}
// process next metacharacter
char metaChar = str.charAt(strPos);
if (metaChar == '+') {
dec.append(' ');
strPos++;
continue;
} else if (metaChar == '%') {
// We throw the original exception - the super will deal with
// it
// try {
char res = (char) Integer.parseInt(
str.substring(strPos + 1, strPos + 3), 16);
if (noSlash && (res == '/')) {
throw new IllegalArgumentException("noSlash");
}
dec.append(res);
strPos += 3;
}
}
如上代码可以很明显看到 + 为什么变成了 空格;%部分可以看到代码截取%后的两位字符,然后放到StringBuilder,自如如上面例子中 voteless%cz,会将cz 以16进制转为 char ,得到的是怪怪的字符;如果是 voteless%c ,%后面只有一位,就会导致异常,使得获取不到value值(null)。