报文传输中的 url 编码问题
url 编码
- 当 url 路径,或者查询参数中带有中文、特殊字符的时候,就需要对 url 进行编码(采用十六进制编码格式)。url 编码的原则是使用安全字符(即没有特殊用途或者特殊意义的字符)去表示那些不安全的字符。
- 对于特殊字符来说,url 编码就是一个字符 ascii 码的十六进制。(ASCII 码使用 8 位二进制数的组合来表示不同的字符,也就是一个字节的大小。其中常规的 ASCII 码,最高位是 0,所以总共可以表示 128 个字符。) 不过稍微有些变动,需要在前面加上
%
。 比如\
,它的 ascii 码是 92,92 的十六进制是 5c,所以\
的 url 编码就是%5c。 - 对于中文来说,url 编码是将其转换为 Unicode 码,再转化为十六进制的 UTF-8 编码,最后加上%。
- url 编码使用后跟十六进制数字的 "%" 替代不安全的 ASCII 字符。
- url 不能包含空格。url 编码通常使用加号(+)或 %20 替代空格。
RFC3986 文档规定,url 中只允许包含以下四种:
- 英文字母(a-zA-Z)
- 数字(0-9)
-_.~
4 个特殊字符- 所有保留字符,RFC3986 中指定了以下字符为保留字符(英文字符):
! * ' ( ) ; : @ & = + $, / ? # [ ]
所谓保留字符,就是在 url 中具有特定意义的字符。
常见 url 编码转换表
编码前 | 编码后 | 编码前 | 编码后 | 编码前 | 编码后 |
---|---|---|---|---|---|
space | %20 | * | %2A | > | %3E |
! | %21 | + | %2B | ? | %3F |
" | %22 | , | %2C | @ | %40 |
# | %23 | - | %2D | [ | %5B |
$ | %24 | . | %2E | \ | %5C |
% | %25 | / | %2F | ] | %5D |
& | %26 | : | %3A | ^ | %5E |
' | %27 | ; | %3B | _ | %5F |
( | %28 | < | %3C | ` | %60 |
) | %29 | = | %3D | { | %7B |
| | %7C | } | %7D | ~ | %7E |
测试需要注意的点
集中存储、监控点上墙等场景,传输的是 url 地址,需要重点关注用户名/密码包含所有保留字符的表现,因为后端在处理 url、从中提取用户名、密码、IP 等信息时,可能会遇到保留字符。
设置设备名称等场景,原则上用户输入可以是任意字符,在HTTP传输过程中,为了确保数据的正确性和可靠性,通常需要对payload进行URL编码处理,这也就导致了所有需要进行HTTP传输的场景,客户端需要将原始用户输入进行URL编码传输、而将从服务端收到的数据进行URL解码显示,其中一个环节出现问题就可能导致传输或显示异常。
此时需要关注所有 url 编解码相关的字符。
可能存在风险的字符:
- 空格
- 所有保留字符
%
本身- 编码后的字符,比如
%20、%21
等
各编程语言的url编解码方式
Python:
urllib.parse.quote
和urllib.parse.unquote
:这是Python标准库中提供的url编解码函数,可以将字符进行url编码或解码。
lang=Python
import urllib.parse
# url编码
url = 'https://www.example.com/search?q=Python 编程'
encoded_url = urllib.parse.quote(url)
print(encoded_url) # https%3A//www.example.com/search%3Fq%3DPython%20%E7%BC%96%E7%A8%8B
# url解码
decoded_url = urllib.parse.unquote(encoded_url)
print(decoded_url) # https://www.example.com/search?q=Python 编程
requests.utils.quote
和requests.utils.unquote
:这是requests库中提供的url编解码函数,与urllib.parse
类似。
lang=Python
import requests.utils
# url编码
url = 'https://www.example.com/search?q=Python 编程'
encoded_url = requests.utils.quote(url)
print(encoded_url) # https%3A//www.example.com/search%3Fq%3DPython%20%E7%BC%96%E7%A8%8B
# url解码
decoded_url = requests.utils.unquote(encoded_url)
print(decoded_url) # https://www.example.com/search?q=Python 编程
Java:
java.net.URLEncoder
和java.net.URLDecoder
:这是Java标准库中提供的URL编解码类,可以将字符串进行URL编码或解码
lang=Java
import java.net.URLEncoder;
import java.net.URLDecoder;
// URL编码
String url = "https://www.example.com/search?q=Java 编程";
String encodedUrl = URLEncoder.encode(url, "UTF-8");
System.out.println(encodedUrl); // https%3A%2F%2Fwww.example.com%2Fsearch%3Fq%3DJava+%E7%BC%96%E7%A8%8B
// URL解码
String decodedUrl = URLDecoder.decode(encodedUrl, "UTF-8");
System.out.println(decodedUrl); // https://www.example.com/search?q=Java 编程
org.apache.commons.codec.net.URLCodec
:这是Apache Commons Codec库中提供的URL编解码类,与java.net.URLEncoder
和java.net.URLDecoder
类似
lang=Java
import org.apache.commons.codec.net.URLCodec;
// URL编码
String url = "https://www.example.com/search?q=Java 编程";
String encodedUrl = new URLCodec().encode(url);
System.out.println(encodedUrl); // https%3A%2F%2Fwww.example.com%2Fsearch%3Fq%3DJava+%E7%BC%96%E7%A8%8B
// URL解码
String decodedUrl = new URLCodec().decode(encodedUrl);
System.out.println(decodedUrl); // https://www.example.com/search?q=Java 编程
C:
C语言中没有标准库提供URL编解码函数,一般手动实现。
lang=C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// URL编码函数
char *urlencode(const char *str) {
const char *hex = "0123456789ABCDEF";
size_t len = strlen(str);
char *buf = malloc(len * 3 + 1), *pbuf = buf;
for (size_t i = 0; i < len; i++) {
if (isalnum(str[i]) || strchr("-_.~", str[i])) {
*(pbuf++) = str[i];
} else if (str[i] == ' ') {
*(pbuf++) = '+';
} else {
*(pbuf++) = '%';
*(pbuf++) = hex[(unsigned char) str[i] >> 4];
*(pbuf++) = hex[(unsigned char) str[i] & 15];
}
}
*pbuf = '\0';
return buf;
}
// URL解码函数
char *urldecode(const char *str) {
size_t len = strlen(str);
char *buf = malloc(len + 1), *pbuf = buf;
for (size_t i = 0; i < len; i++) {
if (str[i] == '+') {
*(pbuf++) = ' ';
} else if (str[i] == '%' && isxdigit(str[i + 1]) && isxdigit(str[i + 2])) {
int ch;
sscanf(&str[i + 1], "%02x", &ch);
*(pbuf++) = ch;
i += 2;
} else {
*(pbuf++) = str[i];
}
}
*pbuf = '\0';
return buf;
}