2023年12月13日发(作者:)

关于下载文件是文件名的空格显示不出来和乱码问题转

在 项目中,我们可以很方便地使用 ile() 方法向客户端输出一个文件。

实际使用 向客户端输出文件流时,却出现了异常:

1、空格问题,当原文件的文件名中含有空格时,将引发客户端获取到的文件名与服务器端不一致。

2、中文字符乱码,准确的是非 ASCII 字符乱码,当原文件的文件名中含有非 ASCII 字符时,将引发客户端获取到的文件名错乱。

3、一些特殊字符不能被正常输出(当然这里我并不是那些不常见的符号)

注意,本文用 C# 代码解决了在目前四种流行浏览器中 输出文件流时文件名的空格及中文字符乱码这两个问题。使用本文的代码,

你将可以让 IE(Internet Explorer)、Opera、Firefox 及 Chrome 的用户享受到没有乱码且支持空格文件名的文件输出引擎,同时支持文件

名中各种像“# $ % ^ &”等常见的符号,如 "" 、“F ile;;!@%#^&” 这样的文件名也可以了。请看下图:

本文下面的内容将描述问题的具体表现,并对相关代码做一些解释;

如果你不需要阅读这些内容,你可以直接下载示例代码。

问题现象:

对于第一个问题

在IE中,当原文件名包含空格时,默认将被改成下划线,即“_”;如果我们在输出文件时对文件名使用 UrlEncode() 对其进行编码,空格将变

成加号,即“+”。

在 Opera 中,文件名不需要经过 UrlEncode() 即可正确地解析,但注意经过了 UrlEncode() 后也与IE一样,空格变成了加号。

很 遗憾, Firefox 似乎并不欢迎含有空格的文件名,它会直接舍弃空格后面的部分。对于上图中的例子,没有进行 UrlEncode() 之

前,Firefox 会得到一个“”的文件名,可以看到,它对文件类型把握并没有错误(只因为这由别外的部分负责);进行 UrlEncode() 之

后,它的结果与 IE、Opera 等一致,空格变成了加号。

对于第二个问题

第二个问题有点复杂了。

当原文件名包含中文或其他非英文字符时,由于编码的错误,默认情况很糟糕,竟然完全是无法辨识的乱码;如果我们在输出文件时对文件

名进行 UrlEncode() 对其进行编码,这些中文将能正确地被显示;

但注意,问题并没有完。在Opera 或 Firefox 中,不需要经过 UrlEncode() 即能正确地显示了;不幸地是,如果经过了 UrlEncode(),它们将

无法正确地解析。

看下面几个图,分别是没有使用 UrlEncode() 编码文件名和使用了 UrlEncode() 的时候,全英文的原文件名的文件输出到客户端的情况:

未进行 UrlEncode() 的中文文件名,IE 浏览器:

已进行 UrlEncode() 的中文文件名,IE 浏览器:

已进行 UrlEncode() 的中文文件名,Opera 浏览器

至于 Firefox 与 Chrome 的图就不贴了,它们与 Opera 基本一致。

问题的解决

我们可以总结如下规律:

Internet Explorer 能在客户端已经UrlEncode() 的字符,包括空格在内;而 Opera 等其他浏览器可以解析未经 UrlEncode() 的直接输出的字

符(这意味着,对于使用Opera或其他客户端的客户,我们不应该对它进行 UrlEncode()编码)

为了正确地编码,我参考一位外国人士的代码,使用并改进了16进制编码方法。参考下面的代码,可以大部分的解决问题。由于 Firefox 默

认不支持中文,特别对 Firefox 用户做了一些处理,在下面的代码中能够体现。

在输出文件地地方使用的代码:

view plaincopy to clipboardprint?

(context != null)

02.{

03. HttpRequest request = t;

04. HttpResponse response = se;

05. //本文件使用了 QueryString 来传递文件名,你也可以不使用

06. if (!OrEmpty(tring["file"])) 07. {

08. //取得客户端正在请求的文件的物理路径

09. //不使用 QueryString 时,你可以使用 alPath 获取

10. string path = h("~/") +

11. ode(tring["file"]).Replace("/", "();

12. if ((path))

13. {

14. string extension = ension(path);

15. tType = GetMimeType(extension);

16. string fileName = eName(path);

17. if (r().IndexOf("msie") > -1)

18. {

19. //当客户端使用IE时,对其进行编码;We should encode the filename when our visitors use IE

20. //使用 ToHexString 代替传统的 UrlEncode();We use "ToHexString" replaced "ode(fileName)"

21. fileName = ToHexString(fileName);

22. }

23. if (r().IndexOf("firefox") > -1)

24. {

25. //为了向客户端输出空格,需要在当客户端使用 Firefox 时特殊处理

26. der("Content-Disposition", "attachment;filename=/"" + fileName + "/"");

27. }

28. else

29. der("Content-Disposition", "attachment;filename=" + fileName);

30. ile(path);

31. ();

32. return;

33. }

34. }

35.}

36.//正在请求的文件不存在;Cannot find the specified file

();

("the data you are wanting to get does not exsit.");

();

if (context != null)

{

HttpRequest request = t;

HttpResponse response = se;

//本文件使用了 QueryString 来传递文件名,你也可以不使用

if (!OrEmpty(tring["file"]))

{

//取得客户端正在请求的文件的物理路径

//不使用 QueryString 时,你可以使用 alPath 获取

string path = h("~/") +

ode(tring["file"]).Replace("/", "();

if ((path))

{

string extension = ension(path);

tType = GetMimeType(extension);

string fileName = eName(path);

if (r().IndexOf("msie") > -1)

{

//当客户端使用IE时,对其进行编码;We should encode the filename when our visitors use IE

//使用 ToHexString 代替传统的 UrlEncode();We use "ToHexString" replaced "ode(fileName)"

fileName = ToHexString(fileName);

}

if (r().IndexOf("firefox") > -1)

{

//为了向客户端输出空格,需要在当客户端使用 Firefox 时特殊处理

der("Content-Disposition", "attachment;filename=/"" + fileName + "/"");

}

else

der("Content-Disposition", "attachment;filename=" + fileName);

ile(path);

();

return;

}

}

} //正在请求的文件不存在;Cannot find the specified file

();

("the data you are wanting to get does not exsit.");

();

下面是核心处理,应该置于上述代码同一文件或可访问的其他类:

view plaincopy to clipboardprint?

01.#region 编码

02.

03.///

04./// 对字符串中的非 ASCII 字符进行编码

05.///

06.///

07.///

static string ToHexString(string s)

09.{

10. char[] chars = Array();

11. StringBuilder builder = new StringBuilder();

12. for (int index = 0; index < ; index++)

13. {

14. bool needToEncode = NeedToEncode(chars[index]);

15. if (needToEncode)

16. {

17. string encodedString = ToHexString(chars[index]);

18. (encodedString);

19. }

20. else

21. {

22. (chars[index]);

23. }

24. }

25.

26. return ng();

27.}

28.

29.///

30./// 判断字符是否需要使用特殊的 ToHexString 的编码方式

31.///

32.///

33.///

e static bool NeedToEncode(char chr)

35.{

36. string reservedChars = "$-_.+!*'(),@=&";

37.

38. if (chr > 127)

39. return true;

40. if (erOrDigit(chr) || f(chr) >= 0)

41. return false;

42.

43. return true;

44.}

45.

46.///

47./// 为非 ASCII 字符编码

48.///

49.///

50.///

e static string ToHexString(char chr)

52.{

53. UTF8Encoding utf8 = new UTF8Encoding();

54. byte[] encodedBytes = es(ng());

55. StringBuilder builder = new StringBuilder();

56. for (int index = 0; index < ; index++)

57. {

58. Format("%{0}", ng(encodedBytes[index], 16)); 59. }

60.

61. return ng();

62.}

63.

64.

65.#endregion

66.

67.

68.///

69./// 根据文件后缀来获取MIME类型字符串

70.///

71./// 文件后缀

72.///

string GetMimeType(string extension)

74.{

75. string mime = ;

76. extension = r();

77. switch (extension)

78. {

79. case ".avi": mime = "video/x-msvideo"; break;

80. case ".bin":

81. case ".exe":

82. case ".msi":

83. case ".dll":

84. case ".class": mime = "application/octet-stream"; break;

85. case ".csv": mime = "text/comma-separated-values"; break;

86. case ".html":

87. case ".htm":

88. case ".shtml": mime = "text/html"; break;

89. case ".css": mime = "text/css"; break;

90. case ".js": mime = "text/javascript"; break;

91. case ".doc":

92. case ".dot":

93. case ".docx": mime = "application/msword"; break;

94. case ".xla":

95. case ".xls":

96. case ".xlsx": mime = "application/msexcel"; break;

97. case ".ppt":

98. case ".pptx": mime = "application/mspowerpoint"; break;

99. case ".gz": mime = "application/gzip"; break;

100. case ".gif": mime = "image/gif"; break;

101. case ".bmp": mime = "image/bmp"; break;

102. case ".jpeg":

103. case ".jpg":

104. case ".jpe":

105. case ".png": mime = "image/jpeg"; break;

106. case ".mpeg":

107. case ".mpg":

108. case ".mpe":

109. case ".wmv": mime = "video/mpeg"; break;

110. case ".mp3":

111. case ".wma": mime = "audio/mpeg"; break;

112. case ".pdf": mime = "application/pdf"; break;

113. case ".rar": mime = "application/octet-stream"; break;

114. case ".txt": mime = "text/plain"; break;

115. case ".7z":

116. case ".z": mime = "application/x-compress"; break;

117. case ".zip": mime = "application/x-zip-compressed"; break;

118. default:

119. mime = "application/octet-stream";

120. break;

121. }

122. return mime;

123.}

#region 编码

///

/// 对字符串中的非 ASCII 字符进行编码

///

///

///

public static string ToHexString(string s)

{

char[] chars = Array();

StringBuilder builder = new StringBuilder();

for (int index = 0; index < ; index++)

{

bool needToEncode = NeedToEncode(chars[index]);

if (needToEncode)

{

string encodedString = ToHexString(chars[index]);

(encodedString);

}

else

{

(chars[index]);

}

}

return ng();

}

///

/// 判断字符是否需要使用特殊的 ToHexString 的编码方式

///

///

///

private static bool NeedToEncode(char chr)

{

string reservedChars = "$-_.+!*'(),@=&";

if (chr > 127)

return true;

if (erOrDigit(chr) || f(chr) >= 0)

return false;

return true;

}

///

/// 为非 ASCII 字符编码

///

///

///

private static string ToHexString(char chr)

{

UTF8Encoding utf8 = new UTF8Encoding();

byte[] encodedBytes = es(ng());

StringBuilder builder = new StringBuilder();

for (int index = 0; index < ; index++)

{

Format("%{0}", ng(encodedBytes[index], 16));

}

return ng();

}

#endregion

///

/// 根据文件后缀来获取MIME类型字符串

///

/// 文件后缀

///

static string GetMimeType(string extension) {

string mime = ;

extension = r();

switch (extension)

{

case ".avi": mime = "video/x-msvideo"; break;

case ".bin":

case ".exe":

case ".msi":

case ".dll":

case ".class": mime = "application/octet-stream"; break;

case ".csv": mime = "text/comma-separated-values"; break;

case ".html":

case ".htm":

case ".shtml": mime = "text/html"; break;

case ".css": mime = "text/css"; break;

case ".js": mime = "text/javascript"; break;

case ".doc":

case ".dot":

case ".docx": mime = "application/msword"; break;

case ".xla":

case ".xls":

case ".xlsx": mime = "application/msexcel"; break;

case ".ppt":

case ".pptx": mime = "application/mspowerpoint"; break;

case ".gz": mime = "application/gzip"; break;

case ".gif": mime = "image/gif"; break;

case ".bmp": mime = "image/bmp"; break;

case ".jpeg":

case ".jpg":

case ".jpe":

case ".png": mime = "image/jpeg"; break;

case ".mpeg":

case ".mpg":

case ".mpe":

case ".wmv": mime = "video/mpeg"; break;

case ".mp3":

case ".wma": mime = "audio/mpeg"; break;

case ".pdf": mime = "application/pdf"; break;

case ".rar": mime = "application/octet-stream"; break;

case ".txt": mime = "text/plain"; break;

case ".7z":

case ".z": mime = "application/x-compress"; break;

case ".zip": mime = "application/x-zip-compressed"; break;

default:

mime = "application/octet-stream";

break;

}

return mime;

}

此外,针对一些浏览器做了一些特殊的处理,已经体现在本文示例代码的注释中。此代码已经能非常完好地解决问题了,在 Internet

Explorer 、Opera、Firefox 及 Chrome 中得到的体验一致,支持中文,支持空格的正常输出。

如果复制代码后运行不正常,可以参考在示例代码文件的处理情况,在这里下载示例代码文件,示例文件是一个 HttpHandler,因此你可能

需要为它在 中做相关配置,关于配置方法,请参考其他资料。你可以按你的需要来修改示例代码