应循环调用 urldecode() 直至无法再解,同时用长度和正则防死循环;还原后需按原始编码(如 UTF-8/GBK)针对性转码 query value 部分;parse_url() 等函数必须在完全解码后调用;根治方法是在短链存储时避免对整个 URL 多余编码。

短链接跳转时 URL 参数里的特殊符号被双重编码
PHP 接收短链接重定向后的目标 URL 时,经常发现 %2520(空格)、%253D(等号)、%252F(斜杠)这类“套娃式编码”。本质是原始 URL 已被编码一次,又被短链服务再次编码 —— 比如用户分享的是 https://a.com?q=hello world,短链服务存入数据库前做了 urlencode(),生成短链后跳转时又对整个 URL 再做了一次 urlencode(),导致最终 PHP 收到的是 https%253A%252F%252Fa.com%253Fq%253Dhello%252Bworld。
还原的关键不是“解一次”,而是“解到没有 %[0-9A-Fa-f]{2} 可解为止”,但不能无限制循环(防恶意构造)。建议用 urldecode() 循环 + 长度判断:
function fully_decode_url($url) {
$prev = '';
while ($url !== $prev) {
$prev = $url;
$url = urldecode($url);
// 防止死循环:若解码后长度没变,且还含 %xx,说明已无效或损坏
if (strlen($url) === strlen($prev) && preg_match('/%[0-9A-Fa-f]{2}/', $url)) {
break;
}
}
return $url;
}
还原后仍显示乱码?检查字符集是否匹配原始编码
短链目标 URL 中的中文、日文等非 ASCII 字符,在原始编码时可能用 UTF-8,也可能用 GBK(尤其老系统或某些国内短链平台)。PHP 默认按字节解码 urldecode(),不自动识别编码。如果还原后是 ä½ å¥½ 这类乱码,大概率是原始 URL 用 UTF-8 编码,但你误以为是 GBK;反之亦然。
- 先确认原始来源:查短链后台日志、或用浏览器开发者工具看跳转前的
Location响应头值 - 用
mb_detect_encoding()辅助判断不可靠,优先以来源为准 - 必要时手动转码:
mb_convert_encoding($decoded, 'UTF-8', 'GBK')或iconv('GBK', 'UTF-8//IGNORE', $decoded) - 注意:不要对整个 URL 调用
mb_convert_encoding(),只转 query string 的 value 部分(比如q=xxx中的xxx),否则会破坏协议、域名、路径中的合法 ASCII 字符
parse_url() 和 parse_str() 处理前必须确保 URL 已完全还原
很多人直接对未还原的编码串调用 parse_url(),结果 parse_url('https%3A%2F%2Fexample.com%3Fx%3D%E4%BD%A0') 返回 ['scheme' => 'https%3A', 'host' => '%2F%2Fexample.com%3Fx%3D%E4%BD%A0'] —— 完全错乱。必须在调用前完成彻底解码。
立即学习“PHP免费学习笔记(深入)”;
还原后,再安全拆解:
$raw_url = $_GET['url'] ?? '';
$decoded = fully_decode_url($raw_url);
$parsed = parse_url($decoded);
if ($parsed === false || empty($parsed['scheme']) || empty($parsed['host'])) {
die('Invalid URL');
}
// 只对 query 部分解码并解析
if (!empty($parsed['query'])) {
parse_str($parsed['query'], $query_params);
// $query_params 现在是关联数组,value 已是原始字符串(如 ['q' => '你好'])
}
短链服务端存储时就该规避双重编码
真正省事的做法,是在生成短链时就堵住源头。入库前不做多余编码:
- 原始 URL 若来自用户输入,先做基础校验(
filter_var($url, FILTER_VALIDATE_URL)),再直接存为原始字符串 - 不要对整个 URL 执行
urlencode()后存储;如需转义用于 SQL,用参数化查询或mysqli_real_escape_string(),而非 URL 编码 - 跳转响应中输出
Location: $stored_url即可,由浏览器自行处理编码/解码逻辑 - 若必须用 URL 编码(例如拼在 query 中作透传参数),只编码「值本身」,而不是整个 URL。例如:
?target=' . urlencode($original_url)—— 这是正确用法
双重编码问题看似是接收端的事,其实八成发生在生成端。还原只是补救,规范存储才是根治点。尤其当短链要支持带 hash(#)或复杂 query 的 URL 时,任意一层多编码都会让 parse_url() 失效或截断。











