Apache 用特殊字符重写我的编码文件名

Apache rewriting my encoded filename with special characters

我在下载包含“ñ”或“Ñ”等特殊字符的文件时遇到问题。

我正在对每个文件名应用 rawurlencode()。但是当我点击路由时,apache 正在重写我的 url.

<td>
  <a href="{{asset("storage/" . rawurlencode($fyi->file))}}" target="_blank">{{$fyi->name}}</a></td>

当我检查 a 标签中的 href 时,我得到了正确的 URL 编码路由,如下图所示。

a tag with encoded url

但是当我点击 link 时,编码丢失了

missing encoded after hitting the link

这是我的 apache 配置

<VirtualHost *:80>
    ServerName xxx.com
    ServerAlias xxx.con

    DocumentRoot /home/dev/xxx.com/public
    AllowEncodedSlashes On


    <Directory /home/dev/xxx.com/public>
        Options +Indexes +FollowSymLinks +MultiViews
        AllowOverride All
    Order allow,deny
    allow from all
    Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/xxx.com-error.log
    CustomLog ${APACHE_LOG_DIR}/xxx.com-access.log combined
    RewriteEngine on
    RewriteCond %{SERVER_NAME} =xxx.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [QSA,L]
</VirtualHost>

这只是浏览器向您很好地显示它。检查浏览器 devtools 或服务器日志中的网络选项卡,您会看到它实际上已编码。


真正的问题是其他问题 - Unicode 规范化。这是一个复杂的话题,请参阅 this and this 答案以获得解释。

我们可以看到这是您问题的解决方案,因为虽然您的 original link doesn't work, this one 有效。我用 N%CC%83 替换了 %C3%91 (这是一个字符“带波浪号的 N”),这是两个字符 - N 后跟一个“组合波浪号” - Ñ (1 个字符)与 (2 个字符)- 看起来相同但编码不同!

tl;dr:在 Unicode 中有多种表示同一事物的方法。您得到的 link 具有 Ñ 作为单个字符 U+00D1 : LATIN CAPITAL LETTER N WITH TILDE,但您的实际文件似乎具有 作为两个字符:U+004E : LATIN CAPITAL LETTER N 后跟 U+0303 : COMBINING TILDE.

为确保其正常工作,最好确保在所有地方都使用“规范化”表示,即“NFC”形式(还有“NFD”,这是您当前的文件名)。请注意,Mac 倾向于使用 NFD,而其他 *nixes 通常使用 NFC,如果您从 Mac 上传文件而不在此过程中转换文件名,这可能是一个问题。

解决方法是运行你的存储文件夹中的convmv工具来转换所有文件的文件名:

convmv -r -i -f utf8 -t utf8 --nfc --notest .

或者(或另外)您可以首先尝试了解不正确的 link 的来源,但是在 Linux 上使用 NFD 文件名无论如何都是错误的秘诀,因为您永远不会知道什么库或客户端或其他什么可能会在某个时候不小心规范化文件名的错误——当你添加一些新功能时,它可能会在几年后作为错误出现,所以最好确保文件名首先使用 NFC地点。


您可能考虑的另一种选择是在数据库中有一个文件元数据索引,而不是用它的实际文件名存储文件(顺便说一句,如果配置错误,这也可能导致安全漏洞)但是一些随机 ID 作为名称,然后通过其 ID 识别它,并在下载时返回 Content-Disposition header 中的原始名称,以便用户在最后获得正确的文件名。为了改善用户体验,您可以创建 URL 之类的 /storage/<ID>/<nice-name>,其中 <nice-name> 仅用于显示目的(使 URL 已经显示它是什么文件)和不用于识别实际文件。