在文件下载对话框中显示非 ASCII 文件名






3.62/5 (15投票s)
2004年11月15日
7分钟阅读

230699

1052
在文件下载对话框中显示非 US-ASCII 文件名的简单方法。
目录
引言
上传/下载文件是 ASP.NET 应用程序中的常见任务。一旦用户将文件上传到 Web 服务器,稍后从服务器下载该文件时,他们会希望在文件下载对话框中看到完全显示的文件名。基本上,开发人员通常会使用“Content-Disposition”报头字段来强制下载,“filename”参数用于建议下载文件的文件名。如果文件名只包含所有 US-ASCII 字符,那么就没有问题,因为在文件下载对话框中显示的文件名与上传时相同。问题只发生在文件名包含非 US-ASCII 字符(如越南语或阿拉伯语…)时,那时它会损坏,并且不会以用户希望的方式显示。解释这个问题的原因是,文件名参数仅限于 US-ASCII。有关 Content-Disposition 字段的完整信息,请参阅 RFC 2183。
图 1:非 US-ASCII 文件名损坏
在本文中,我提出了三种简单的替代方法来解决此问题,以便在文件下载对话框中准确显示非 US-ASCII 文件名。
- 编码文件名
- URL 重写
- “编码单词”机制
在每个部分的末尾,我还会简要解释何时可以使用该解决方案。
图 2:非 US-ASCII 文件名正确显示
编码文件名
在此解决方案中,我们将使用 html <a>
元素创建指向请求文件的链接,而不是使用 Content-Disposition 报头字段来开发下载功能。但是,我们需要注意的一点是,浏览器通常以 UTF-8 发送 URL,因此当文件上传到服务器时,需要在保存之前对文件名进行编码。下面是用于编码文件名的代码片段。
public static string EncodeFilename(string filename)
{
UTF8Encoding utf8 = new UTF8Encoding();
byte[] bytes = utf8.GetBytes(filename);
char[] chars = new char;
for(int index=0; index<bytes.Length; index++)
{
chars[index] = Convert.ToChar(bytes[index]);
}
string s = new string(chars);
return s;
}
此解决方案很简单,当文件下载对话框不强制显示时,我们可以使用它。但是,我们还需要做一些额外的工作来控制文件名重复,因为应用程序的用户可能会上传大量同名文件。此外,在某些情况下,当编码值包含不允许用于命名文件的特殊字符时,此解决方案无法正常工作。
URL 重写
如前所述,开发人员通常会使用“Content-Disposition”报头字段来强制下载。当我们查看邮件用户代理 (MUA) 处理 Content-Disposition 报头字段的方式时,我们可以看到接收的 MUA 使用 filename 参数值作为文件下载中实际文件名的基础。如果此参数不存在,MUA 可能会显示负责将下载的文件内容写入客户端的网页名称(在 ASP.NET 应用程序中,通常是 aspx 页面的名称)。因此,此解决方案的想法是,我们首先请求下载的文件,然后当它到达服务器时,URL 将被重写到一个 aspx 页面,该页面负责读取原始请求的文件并将其发送回客户端。在此 aspx 页面中,我们使用 Content-Disposition 报头字段而不指定 filename 参数。
总的来说,URL 重写可以在 IIS 级别或 ASP.NET 级别实现,而 URL 重写仅在请求成功从 IIS 路由到 ASP.NET 引擎时在 ASP.NET 级别发生。众所周知,只有带有 .aspx、.ascx、.ashx… 等扩展名的页面的请求才会由 ASP.NET 引擎处理。此外,我们作为开发人员并不知道用户上传到服务器的文件类型,因此 IIS 级别的 URL 重写很可能是答案。本文中,我将不介绍如何在 IIS 中使用 ISAPI 过滤器实现 URL 重写。但是,有一些第三方 ISAPI 过滤器可供使用。演示中,我使用的是 ISAPI_Rewrite Live 版本,它简单且免费。您可以从 这里 下载最新版本。
安装 ISAPI_Rewrite 后,我们可以在 *httpd.ini* 文件中定义重写规则,该文件应出现在 ISAPI_Rewrite 安装目录中。为方便起见,我们只定义一个重写规则。
RewriteRule (/Sample2/Download/.*?)
(id=[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})
/Sample2/Download.aspx\?$2 [L]
让我简要解释一下这个规则:当用户请求 Download 目录中的任何文件时,URL 将被重写为负责读取文件并将其发送回客户端的 *Download.aspx* 页面。这里,RewriteRule
指令用于定义单个重写规则。例如,用户通过 URL 下载 *report.doc* 文件:
https:///Sample2/Download/report.doc?id=647faba9-a223-45d3-90d5-3bc4de95bd39
在 IIS 中,URL 将被重写为:
https:///Sample2/Download.aspx?id=647faba9-a223-45d3-90d5-3bc4de95bd39
通过此解决方案,我们无需在代码中进行任何进一步的文件名处理,问题即可解决。但是,在 IIS 中安装第三方组件可能出于某些原因不被大家所接受,在这种情况下,“编码单词”机制可能是答案。
“编码单词”机制
众所周知,文件名参数仅限于 US-ASCII。因此,如果文件名包含任何非 US-ASCII 字符,则必须对其进行编码才能在文件下载对话框中准确显示。RFC 2231、2184 定义了 RFC 2047 中编码单词机制的扩展,以提供一种指定 US-ASCII 以外字符集的参数值的方法。编码机制很简单。对于特定文件名,所有非 US-ASCII 字符以及与字母数字和保留字符不同的字符都将替换为 *%xx* 编码,其中 *xx* 是代表该字符的十六进制值。下面是用于编码字符的代码片段。
private static string ToHexString(char chr)
{
UTF8Encoding utf8 = new UTF8Encoding();
byte[] encodedBytes = utf8.GetBytes(chr.ToString());
StringBuilder builder = new StringBuilder();
for(int index=0; index<encodedBytes.Length; index++)
{
builder.AppendFormat("%{0}",Convert.ToString(encodedBytes[index], 16));
}
return builder.ToString();
}
例如,如果原始文件名是 Bản Kiểm Kê.doc
(要正确查看文件名,您应该在 Web 浏览器中选择 Unicode (UTF-8) 编码),则编码值如下所示:B%e1%ba%a3n%20Ki%e1%bb%83m%20K%c3%aa.doc
,然后 Content-Disposition 字段指定如下:
Content-Disposition:
attachment; filename=B%e1%ba%a3n%20Ki%e1%bb%83m%20K%c3%aa.doc
因此,在这种方式下,我们需要提供一些代码在将文件名传递给 Content-Disposition 字段之前对其进行编码,在我看来,这是一个不错的选择,因为我们不需要安装任何第三方组件。但是,我们应该意识到,“编码单词”机制仅在客户端现有的 MIME 处理器理解编码的参数值时才有效,否则文件名将无法按预期正确显示。此外,根据 RFC 2231 和 2184,这些文档中定义的扩展不应随意使用,它们应保留在确实有需要的情况下。有关更多信息,请参阅 RFC 2231,2184。
运行示例代码
下载内容包含三个 Web 应用程序,用于演示这三种解决方案。要尝试演示应用程序,请在 IIS 中创建三个 Web 虚拟目录,并将它们指向 *Sample1*、*Sample2* 和 *Sample3*。起始页应为 *ListDocument.aspx* 页面。对于 *Sample2*,您还需要下载并安装 ISAPI_Rewrite,并将上述重写规则添加到 *httpd.ini* 文件中。
ASPNET 帐户需要对每个应用程序的 *Data* 和 *Download* 目录具有读/写访问权限,并且为了简单起见,我使用 XML 作为后端数据存储。
结论
目前,文件名参数仅限于 US-ASCII,因此我们需要做一些额外的工作才能在文件下载对话框中准确显示文件名。我们希望这一限制将来有一天能够得到解决,使我们能够像使用 US-ASCII 字符一样轻松地使用非 US-ASCII 字符。
参考文献
- RFC:2183、2184、2231、2047。
- ISAPI_Rewrite.