项目中有个ftp组件,在下载中文命名的文件时,总抛出错误“Access is denied, cannot be found the file specified”,英文命名的文件却能够正常下载。首先想到的是编码问题,将文件名做了一次UrlEncode,仍是同样的错误。后来查找代码,发现使用了非标准的ftp command。
先看一下下载代码部分:
2 ***********code etc**************
3 SetBinaryMode(true);
4 OpenDataSocket();
5
6 bytes_total = 0;
7
8 _fileSize = GetFileSize(remoteFileName);
9
10 Byte[] cmd = Encoding.UTF8.GetBytes(("RETR ").ToCharArray());
11 byte[] bytes = Encoding.UTF8.GetBytes(remoteFileName);
12 byte[] content = Encoding.Convert(Encoding.UTF8, Encoding.Default, bytes);
13 int totalLength = cmd.Length + content.Length;
14 byte[] re = new byte[totalLength + 2];
15 for (int i = 0; i < cmd.Length; i++)
16 {
17 re[i] = cmd[i];
18 }
19 for (int i = 0; i < content.Length; i++)
20 {
21 re[cmd.Length + i] = content[i];
22 }
23 re[totalLength] = 13;
24 re[totalLength + 1] = 10;
25
26 main_sock.Send(re, re.Length, 0);
27
28 ReadResponse();
29 ***********code etc**************
30 }
抛出问题的语句做了下标注。此处是获取远程服务器上文件大小。具体代码:
2 /// 获取文件大小
3 /// </summary>
4 /// <param name="filename">文件名</param>
5 /// <returns>文件大小</returns>
6 public long GetFileSize(string fileName){
7 Connect();
8 SendCommand("SIZE " + fileName);
9 ReadResponse();
10
11 if (response != 213) throw new ServerException(responseStr);
12
13 return Int64.Parse(responseStr.Substring(4), CultureInfo.CurrentCulture);
14 }
其中,SendCommand函数为
2 Byte[] cmd = Encoding.ASCII.GetBytes((command + "\r\n").ToCharArray());
3 main_sock.Send(cmd, cmd.Length, 0);
4 }
socked发出ftp的size命令后,得到的响应始终不会是213,抛出的异常就是找不到指定的文件,不论文件名是否做过unicode编码。
在SendCommand函数中,可以看到,命令被作为ascii编码的串进行字节转换。换成Unicode?服务器会抛出超时错误:“Timed out waiting on server to respond”。UTF8?服务器则会抛出与ASCII编码一样的错误。
后来查阅了相关的文档,发现ftp的size命令为非标准的command。在Ascii编码体系中,支持size command来获取文件大小。一些Unix的ftp服务中,已经不支持非标准的命令。size存在与ascii编码体系中,所以,如果是非ascii编码的文件名,通过size是获取文件大小,服务器就会返回550错误:"Access is denied, cannot be found the file specified"。
问题总算找到了,中文等不在ascii码范围,发送size命令,ftp服务器(支持非标准ftp command,否则ftp server会返回500错误:不能识别的命令)会将文件名当成ascii码解析查找,中文文件名自然是查找不到,进行urlecode之后,经过一系列的字节转换,发送到ftp server之后,相当于文件名做了变更,自然找不到文件。
接下来就是修改问题了,文件大小的计算放在了文件被下载到本地后,而没有放在下载前,同时,编码统一为UFT8。至此解决完成。