【问题标题】:unicode characters in image URL - 404图片 URL 中的 Unicode 字符 - 404
【发布时间】:2026-02-04 02:45:02
【问题描述】:

我正在尝试打开名称中包含拉丁字符的图像 (113_Atlético Madrid)。

我通过使用 PHP 函数 rawurlencode() 对其名称进行编码来保存它,所以现在它的新名称是 113_Atl%C3%A9tico%20Madrid。但是,当我尝试通过此 URL 打开它时,例如 mysite.com/images/113_Atl%C3%A9tico%20Madrid.png 我收到 404 错误。

我该如何解决这个问题?

PHP 代码:

if(isset($_FILES['Team'])){
    $avatar = $_FILES['Team'];
    $model->avatar = "{$id}_".rawurlencode($model->name).".png";
    if(!is_file(getcwd()."/images/avatars/competitions/{$model->avatar}")){
        move_uploaded_file($avatar['tmp_name']['avatar'], getcwd()."/images/avatars/teams/{$model->avatar}");
    }
}

【问题讨论】:

  • 你需要使用rawurldecode:php.net/manual/en/function.rawurldecode.php
  • @Hackerman 你能解释一下为什么我需要解码 url 吗?
  • 在那段代码中......您正在创建一个编码文件名......然后您要求该文件存在,如果不存在,则在该文件夹中创建文件,对吗?... .现在您确定检查文件是否已上传?...我认为如果您不使用rawurlencode,您的代码应该可以工作。想一想,您正在使用以下名称保存图像:113_Atl%C3%A9tico%20Madrid.png 对吗?然后当您尝试从浏览器打开该图像时,即使文件存在,您的浏览器也会将其解释为:113_Atl%C3%A9tico%20Madrid.png113_Atlético Madrid.png,即为什么它会返回 404 错误。
  • @Hackerman 当我解码它时,它的新名称变成了113_Atlأ©tico Madrid 而不是113_Atlético Madrid,有什么建议吗?

标签: php html unicode url-encoding


【解决方案1】:

%-encoding 用于 URL。文件名不是 URL。您使用以下表格:

http://example.org/images/113_Atl%C3%A9tico%20Madrid.png

在 URL 中,网络服务器会将其解码为类似以下的文件名:

/var/www/example-site/data/images/113_Atlético Madrid.png

当您准备将文件名放入 URL 时,您应该使用 rawurlencode(),但您不应该使用它来准备用于磁盘存储的文件名。

这里还有一个额外的问题,即在磁盘上存储非 ASCII 文件名是跨平台不可靠的。特别是如果您在 Windows 服务器上运行,像 move_uploaded_file() 这样的 PHP 文件 API 很可能会使用您不想要的编码,并且您最终可能会得到像 113_Atlético Madrid.png 这样的文件名。

这不一定很容易解决,但您可以使用任何形式的编码,甚至是 %-encoding。因此,如果您坚持使用当前的 rawurlencode() 来制作文件名:

/var/www/example-site/data/images/113_Atl%C3%A9tico%20Madrid.png

没关系,但您必须使用 double-rawurlencode 来生成匹配的 URL:

http://example.org/images/113_Atl%25C3%25A9tico%2520Madrid.png

但无论如何,将潜在用户提供的任意字符串作为文件名的一部分包含在内是非常危险的。您可能会受到目录遍历攻击,其中名称包含类似/../../ 的字符串以访问目标目录之外的文件系统。 (而且这些攻击通常会升级为针对 PHP 应用程序的执行任意代码攻击,这些应用程序通常以弱权限部署。)您最好使用完全合成的名称,正如 @MatthewBrown 建议的 (+1)。

(请注意,这仍然不是允许用户文件上传的安全问题的结束,事实证明这是一个非常难以正确实现的功能。内容嗅探和插件仍然存在问题,可以允许图像文件被重新解释为其他类型的文件, 导致跨站点脚本问题. 为了防止这种情况发生的所有可能性, 最好只从单独的主机名提供用户提供的文件, 这样针对该主机的 XSS 不会让你针对主站点的 XSS。)

【讨论】:

    【解决方案2】:

    如果您不需要保留文件名(并且通常有充分的理由不这样做),那么最好简单地重命名整个文件名。当前时间戳是一个合理的选择。

    if(isset($_FILES['Team'])){
        $avatar = $_FILES['Team'];
        $date = new DateTime();
        $model->avatar = "{$id}_".$date->format('Y-m-d-H-i-sP').".png";
        if(!is_file(getcwd()."/images/avatars/competitions/{$model->avatar}")){
            move_uploaded_file($avatar['tmp_name']['avatar'], getcwd()."/images/avatars/teams/{$model->avatar}");
        }
    }
    

    毕竟,文件在上传之前被调用应该不是那么重要,更重要的是,如果两个用户有一张名为“me.png”的图片,那么冲突。

    如果您同意对文件名进行编码的想法,那么我只能为您指出其他答案:

    【讨论】:

    • 这是一个不错的替代解决方案,但它会导致我更改数千张图片的名称。
    • 哎哟。我总是尝试将图像文件名存储在数据库中,因此它的名称无关紧要。我可以每天更改命名方法,没关系。
    最近更新 更多