Delphi 7中的Indy 10.6.2 idFTP-接收文件时文件名中的本机符号出现问题-可能是DefStringE



我将Indy 10.6.2与Delphi 7和Windows 10一起使用64位波兰语。

我运行一个FTP服务器: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.

也许还有有趣的DFM部分:

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.Get()内部的UTF8Encode()之后,我在服务器端得到了更多的问号。现在我的代码如下所示:(我在Form1上检查AFileName。标题只是为了快速调试)

...
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;

背景

IOHandler的DefStringEncoding需要设置为通过套接字传输字符串时要使用的字节编码。Indy将通过使用DefStringEncoding将其编码为字节来发送Unicode字符串。相反,Indy将通过使用DefStringEncoding将接收到的字节解码为Unicode来读取Unicode字符串。

在Delphi2007和更早的版本中,有一个额外的步骤。IOHandler的DefAnsiEncoding需要设置为要与AnsiStrings一起使用的编码。Indy将发送AnsiString,方法是首先使用DefAnsiEncoding将其从ANSI解码为Unicode,然后使用DefStringEncoding发送该Unicode。相反,Indy将首先使用DefStringEncoding读取Unicode字符串,然后使用DefAnsiEncoding将该Unicode编码为ANSI,从而读取AnsiString

默认情况下,DefStringEncodingIndyTextEncoding_ASCII(出于历史原因),DefAnsiEncodingIndyTextEncoding_OSDefault

在客户端

如果TIdFTP确定服务器支持UTF-8,它将自动将IOHandler的DefStringEncoding切换为IndyTextEncoding_UTF8。在没有UTF-8扩展的情况下,FTP协议需要使用ASCII。所以,你真的不应该干扰客户端的DefStringEncoding

您最初将远程文件名作为波兰语编码的AnsiString传入,因此将DefAnsiEncoding设置为IndyTextEncoding_OSDefault在设置为波兰语区域设置的操作系统上是有意义的。在DefStringEncoding设置为IndyTextEncoding_UTF8的情况下,应该向服务器传输正确的UTF-8。

后来您将客户端更改为传入UTF-8编码的AnsiString,但将DefAnsiEncoding设置为IndyTextEncoding_8Bit,因此AnsiString无法正确转换为Unicode,因此后续转换为UTF-8的格式不正确。在这种情况下需要将CCD_ 34设置为CCD_。我建议将DefAnsiEncoding设置为IndyTextEncoding_OSDefault,并使用操作系统区域设置编码的AnsiString,除非您有令人信服的理由不这样做。

在服务器端

只有当客户端发出OPTS UTF-8 <NLST>OPTS UTF8 ON命令(TIdFTP会发出命令,但其他客户端可能不会发出命令)时,TIdFTPServer才会自动将IOHandler的DefStringEncoding切换到IndyTextEncoding_UTF8。注意,这不符合RFC 2640,Indy还没有完全实现它。

您将DefStringEncodingDefAnsiEncoding都设置为IndyTextEncoding_UTF8,这在大多数情况下通常是可以的。在暴露对AnsiStrings的访问权限的事件中,您将使用UTF-8编码的AnsiStrings。

但是,在您的Delphi版本中,OnRetrieveFile事件使用WideString作为其AFileName参数。在服务器以AnsiString的形式从套接字读取文件名后,它会按原样传递给事件处理程序,并使用RTL自己的转换逻辑将其转换为WideString,默认情况下使用操作系统的区域设置。因此,在您的情况下,AnsiString是UTF-8编码的,但操作系统区域设置是波兰语,您最终会得到格式错误的转换。幸运的是,您可以通过提前调用RTL的SetMultiByteConversionCodePage()函数来执行AnsiString&lt-&gt;使用代码页65001(UTF-8)进行WideString转换。

然而,TIdFTPServer的一些事件在Delphi2007和更早版本中使用了WideString参数,因此TIdFTPServer的内部有相当多的区域依赖于RTL的默认AnsiString&lt-&gt;CCD_ 63转换。因此,我建议在服务器端也将DefAnsiEncoding设置为IndyTextEncoding_OSDefault,除非你有充分的理由不这样做。如果你想强制DefStringEncodingIndyTextEncoding_UTF8,而不管OPTS命令如何,那没关系(前提是你的客户端只发送UTF-8编码的路径)。


话虽如此,试试这个:

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 FTPServerConnect(ASender: TIdContext);
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;
try
// FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
// FTPClient.IOHandler.DefAnsiEncoding := IndyTextEncoding_OSDefault;
FTPClient.Get('sample ąęśćłó', 'C:węzły.cpy', True, False);
{ or:
//FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
FTPClient.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
FTPClient.Get(UTF8Encode('sample ąęśćłó'), 'C:węzły.cpy', True, False);
}
finally
FTPClient.Disconnect;
end;
end;
procedure TForm1.FTPServerConnect(ASender: TIdContext);
begin
ASender.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
//ASender.Connection.IOHandler.DefAnsiEncoding := IndyTextEncoding_OSDefault;
end;
procedure TForm1.FTPServerUserLogin(ASender: TIdFTPServerContext;
const AUsername, APassword: String; var AAuthenticated: Boolean);
begin
AAuthenticated := (AUsername = 'new') and (APassword = 'pass');
end;
procedure TForm1.FTPServerRetrieveFile(ASender: TIdFTPServerContext;
const AFileName: WideString; var VStream: TStream);
begin
VStream := TFileStream.Create(AFileName + '.serv', fmOpenRead);
end;
end.

或者这个:

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 FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FTPServerConnect(ASender: TIdContext);
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.FormCreate(Sender: TObject);
begin
SetMultiByteConversionCodePage(CP_UTF8);
end;
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;
try
// FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
// FTPClient.IOHandler.DefAnsiEncoding := IndyTextEncoding_OSDefault;
FTPClient.Get('sample ąęśćłó', 'C:węzły.cpy', True, False);
{ or:
//FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
FTPClient.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
FTPClient.Get(UTF8Encode('sample ąęśćłó'), 'C:węzły.cpy', True, False);
}
finally
FTPClient.Disconnect;
end;
end;
procedure TForm1.FTPServerConnect(ASender: TIdContext);
begin
ASender.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
ASender.Connection.IOHandler.DefAnsiEncoding := IndyTextEncoding_UTF8;
end;
procedure TForm1.FTPServerUserLogin(ASender: TIdFTPServerContext;
const AUsername, APassword: String; var AAuthenticated: Boolean);
begin
AAuthenticated := (AUsername = 'new') and (APassword = 'pass');
end;
procedure TForm1.FTPServerRetrieveFile(ASender: TIdFTPServerContext;
const AFileName: WideString; var VStream: TStream);
begin
VStream := TFileStream.Create(AFileName + '.serv', fmOpenRead);
end;
end.

最后我放弃了D7下混乱的TextEncoding。失去了三天,我在同一个地方。

经过长时间的调查,无论我选择哪种编码类型,我都选择了TIdASCIIEncoding.GetBytes()。在这里,任何高于$007F(127)的Char都被转换为"这对于ASCII是显而易见的,但对于UTF8和ANSI则不是。

只要我能在两个应用程序之间进行通信,我就会使用更长的路来解决这个问题。我知道这是在打补丁,但至少有效。

"解决方案":

FTPClient.Get()内部,我放置由IdEncoderMIME.EncodeString()编码的字符串,而不是包含国家符号的源字符串,并且在FTPServerRetrieveFile()内部的服务器端,我删除第一个字符"/"然后我的字符串被IdDecoderMIME.DecodeString()解码,最后我在另一边得到了正确的文件名。

客户端:

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);

丑陋但有效。。。

最新更新