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 中只允许包含以下四种:

  1. 英文字母(a-zA-Z)
  2. 数字(0-9)
  3. -_.~ 4 个特殊字符
  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.quoteurllib.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.quoterequests.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.URLEncoderjava.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.URLEncoderjava.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;
}