Delphi 7中的Indy 10.6.2 idFTP-接收文件时文件名中的本机符号出现问题-可能是DefStringEncoding的错误行为
我正在使用Indy 10.6.2以及Delphi 7和Windows 10 64位波兰语 我运行一个FTP服务器:Delphi 7中的Indy 10.6.2 idFTP-接收文件时文件名中的本机符号出现问题-可能是DefStringEncoding的错误行为,delphi,delphi-7,indy,indy10,Delphi,Delphi 7,Indy,Indy10,我正在使用Indy 10.6.2以及Delphi 7和Windows 10 64位波兰语 我运行一个FTP服务器:FTPServer:TIdFTPServer,通过使用FTPClient:TIdFTP我试图获取一个文件名中包含国家符号的文件。不幸的是,在调用Get()函数后,在服务器端的OnRetrieveFile事件中,我没有在AFileName中使用国家符号,而是使用问号,这显然会导致其他异常 对于测试,我在同一台机器上和同一个应用程序中同时运行服务器和客户端,以消除任何其他干扰 在客户端,
FTPServer:TIdFTPServer
,通过使用FTPClient:TIdFTP
我试图获取一个文件名中包含国家符号的文件。不幸的是,在调用Get()
函数后,在服务器端的OnRetrieveFile
事件中,我没有在AFileName
中使用国家符号,而是使用问号,这显然会导致其他异常
对于测试,我在同一台机器上和同一个应用程序中同时运行服务器和客户端,以消除任何其他干扰
在客户端,我已经尝试过:
FTPClient.DefStringEncoding := IndyTextEncoding_UTF8;
FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
FTPClient.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
在服务器端,我尝试了:
ASender.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
ASender.Connection.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
它们都没有任何区别。我还尝试了其他编码,但也失败了
下面是我的测试应用程序,以及客户端和服务器的文本DFM值
unit Glowny_FTP;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, IdExplicitTLSClientServerBase, IdFTP, ZLibCompression, IdGlobal,
IdIOHandler, IdIOHandlerStream, IdIOHandlerSocket, IdIOHandlerStack,
IdCustomTCPServer, IdTCPServer, IdCmdTCPServer, IdFTPServer, IdContext,
IdAntiFreezeBase, IdAntiFreeze;
type
TForm1 = class(TForm)
Button1: TButton;
FTPClient: TIdFTP;
FTPServer: TIdFTPServer;
IdAntiFreeze1: TIdAntiFreeze;
procedure Button1Click(Sender: TObject);
procedure FTPServerUserLogin(ASender: TIdFTPServerContext;
const AUsername, APassword: String; var AAuthenticated: Boolean);
procedure FTPServerRetrieveFile(ASender: TIdFTPServerContext;
const AFileName: WideString; var VStream: TStream);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
FTPServer.DefaultPort := 1350;
FTPServer.Active := True;
FTPClient.Host := '127.0.0.1';
FTPClient.Port := 1350;
FTPClient.Username := 'new';
FTPClient.Password := 'pass';
FTPClient.Connect;
// FTPClient.DefStringEncoding := IndyTextEncoding_UTF8;
// FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
// FTPClient.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
FTPClient.Get('sample ąęśćłó','C:\węzły.cpy',True,False); // <-- filename containing national symbols
FTPClient.Disconnect;
end;
procedure TForm1.FTPServerUserLogin(ASender: TIdFTPServerContext;
const AUsername, APassword: String; var AAuthenticated: Boolean);
begin
if (APassword='pass') then
begin
AAuthenticated := True;
end else AAuthenticated := False;
end;
procedure TForm1.FTPServerRetrieveFile(ASender: TIdFTPServerContext;
const AFileName: WideString; var VStream: TStream);
begin
// ASender.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
// ASender.Connection.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
VStream := TFileStream.Create(AFileName+'.serv',fmOpenRead); //<-- here I get: "/sample ??????" without UTF8 and "/sample ????" with UTF8
end;
end.
背景
IOHandler的DefStringEncoding
需要设置为通过套接字传输字符串时要使用的字节编码。Indy将通过使用DefStringEncoding
将Unicode字符串编码为字节来发送该字符串。相反,Indy将通过使用DefStringEncoding
将接收到的字节解码为Unicode来读取Unicode字符串
在Delphi2007及更早的版本中,还涉及到一个额外的步骤。IOHandler的devansiencoding
需要设置为要与ansisting
s一起使用的编码。Indy将发送一个ansisting
,首先使用devansiencoding
将其从ANSI解码为Unicode,然后使用DefStringEncoding
发送该Unicode。相反,Indy将先使用DefStringEncoding
读取Unicode字符串,然后使用devansiencoding
将该Unicode编码为ANSI,从而读取ansisting
默认情况下,DefStringEncoding
是IndyTextEncoding\u ASCII
(出于历史原因),而defansencoding
是IndyTextEncoding\u OSDefault
在客户端
TIdFTP
将自动将IOHandler的DefStringEncoding
切换到IndyTextEncoding\u UTF8
,如果它确定服务器支持UTF-8。如果没有UTF-8扩展,FTP协议需要使用ASCII。因此,您真的不应该完全弄乱客户端的DefStringEncoding
您最初是以波兰语编码的ansisting
形式传入远程文件名,因此将devansiencoding
设置为IndyTextEncoding\u OSDefault
在设置为波兰语地区的操作系统上是有意义的。当DefStringEncoding
设置为IndyTextEncoding\u UTF8
时,应将适当的UTF-8传输到服务器
后来,您将客户端更改为传入UTF-8编码的ansisting
,但您将devansiencoding
设置为IndyTextEncoding_8Bit
,因此ansisting
无法正确转换为Unicode,因此后续到UTF-8的转换格式不正确<在这种情况下,code>devansiencoding需要设置为IndyTextEncoding\u UTF8
。我建议将devansiencoding
设置为IndyTextEncoding\u OSDefault
并使用OS locale encodedansisting
s,除非您有充分理由不这样做
在服务器端
TIdFTPServer
仅当客户端发出OPTS UTF-8
或OPTS UTF8 ON
命令时,才会自动将IOHandler的DefStringEncoding
切换到IndyTextEncoding\u UTF8
。请注意,这与Indy尚未完全实施的标准不符
您正在将DefStringEncoding
和devansiencoding
设置为IndyTextEncoding\u UTF8
,这通常在大多数情况下都是正常的。在公开访问AnsiString
s的事件中,您将以UTF-8编码的AnsiString
s结束
但是,在您的Delphi版本中,OnRetrieveFile
事件使用WideString
作为其AFileName
参数。服务器从套接字读取文件名作为AnsiString
后,它会按原样传递给事件处理程序,使用RTL自己的转换逻辑(默认情况下使用操作系统的区域设置)将其转换为WideString
。因此,在您的情况下,AnsiString
是UTF-8编码的,但操作系统的语言环境是波兰语的,您最终将得到格式错误的转换。幸运的是,您可以通过预先调用RTL函数来缓解这个问题,使用代码页65001(UTF-8)执行ansisting
WideString
转换
但是,在Delphi 2007和更早版本中,一些TIdFTPServer
的事件使用了WideString
参数,因此有相当多的TIdFTPServer
的内部区域依赖于RTL的默认转换。因此,我建议将服务器端的devansiencoding
设置为IndyTextEncoding\u OSDefault
,除非您有充分的理由不这样做。如果您想强制DefStringEncoding
到IndyTextEncoding\u UTF8
而不考虑OPTS
命令,这是可以的(前提是您的客户端只发送UTF-8编码路径)
话虽如此,请尝试以下方法:
unit Glowny\u FTP;
接口
使用
窗口、消息、系统工具、变体、类、图形、控件、窗体、,
对话框、StdCtrls、IdBaseComponent、IdComponent、IdTCPConnection、,
IdTCPClient、IdExplicitTLSClientServerBase、IdFTP、ZL
object FTPClient: TIdFTP
Passive = True
ConnectTimeout = 0
DataPort = 20
Password = 'TaJnEH1aS2loD3oSt4ePu'
TransferType = ftBinary
NATKeepAlive.UseKeepAlive = False
NATKeepAlive.IdleTimeMS = 0
NATKeepAlive.IntervalMS = 0
ProxySettings.ProxyType = fpcmNone
ProxySettings.Port = 0
Left = 104
Top = 72
end
object FTPServer: TIdFTPServer
Bindings = <>
DefaultPort = 1350
TerminateWaitTime = 100
CommandHandlers = <>
ExceptionReply.Code = '500'
ExceptionReply.Text.Strings = (
'Unknown Internal Error')
Greeting.Code = '220'
Greeting.Text.Strings = (
'Indy FTP Server ready.')
MaxConnectionReply.Code = '300'
MaxConnectionReply.Text.Strings = (
'Too many connections. Try again later.')
ReplyTexts = <>
ReplyUnknownCommand.Code = '500'
ReplyUnknownCommand.Text.Strings = (
'Unknown Command')
PathProcessing = ftppUnix
AnonymousAccounts.Strings = (
'anonymous'
'ftp'
'guest')
OnUserLogin = FTPServerUserLogin
OnRetrieveFile = FTPServerRetrieveFile
SITECommands = <>
MLSDFacts = []
ReplyUnknownSITCommand.Code = '500'
ReplyUnknownSITCommand.Text.Strings = (
'Invalid SITE command.')
Left = 104
Top = 8
end
...
FTPClient.Connect;
FTPClient.DefStringEncoding := IndyTextEncoding_UTF8;
FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
FTPClient.IOHandler.DefAnsiEncoding := IndyTextEncoding_8Bit;
FTPClient.Get(UTF8Encode('sample ąęśćłó'),'C:\węzły.cpy',True,False);
FTPClient.Disconnect;
procedure TForm1.FTPServerRetrieveFile(ASender: TIdFTPServerContext;
const AFileName: WideString; var VStream: TStream);
begin
Form1.Caption := AFileName;
VStream := TFileStream.Create('C:\tymczas z węzłami obl.aqr',fmOpenRead);
// VStream := TFileStream.Create(AFileName+'.serv',fmOpenRead);
end;
procedure TForm1.FTPServerConnect(AContext: TIdContext);
begin
AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
AContext.Connection.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
end;
S := IdEncoderMIME1.EncodeString('sample ąęśćłó',IndyTextEncoding_UTF8,IndyTextEncoding_UTF8);
FTPClient.Get(S,'C:\węzły.cpy',True,False);
S := Copy(AFileName,2,Length(AFileName));
S := IdDecoderMIME1.DecodeString(S,IndyTextEncoding_UTF8,IndyTextEncoding_UTF8);