我正在使用Indy 10.6.2和Delphi 7以及64位Windows 10和波兰语。
我运行一个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;
发布于 2021-04-24 07:30:10
背景
在通过套接字传输字符串时,需要将IOHandler的DefStringEncoding
设置为要使用的字节编码。Indy将通过使用DefStringEncoding
将其编码为字节来发送Unicode字符串。相反,Indy将通过使用DefStringEncoding
将接收到的字节解码为Unicode来读取Unicode字符串。
在Delphi2007和更早的版本中,有一个额外的步骤。需要将IOHandler的DefAnsiEncoding
设置为您希望与AnsiString
一起使用的编码。Indy将发送一个AnsiString
,首先使用DefAnsiEncoding
将其从ANSI解码为Unicode,然后使用DefStringEncoding
发送该Unicode。相反,Indy将通过首先使用DefStringEncoding
读入Unicode字符串,然后使用DefAnsiEncoding
将该Unicode编码为AnsiString
来读入ANSI。
默认情况下,DefStringEncoding
为IndyTextEncoding_ASCII
(由于历史原因),DefAnsiEncoding
为IndyTextEncoding_OSDefault
。
在客户端
如果TIdFTP
确定服务器支持UTF-8,它会自动将IOHandler的DefStringEncoding
切换到IndyTextEncoding_UTF8
。在没有UTF-8扩展的情况下,FTP协议需要使用ASCII。所以,你真的不应该弄乱客户端的DefStringEncoding
。
您最初将远程文件名作为波兰语编码的AnsiString
传入,因此在设置为波兰语地区的操作系统上,将DefAnsiEncoding
设置为IndyTextEncoding_OSDefault
是有意义的。在DefStringEncoding
设置为IndyTextEncoding_UTF8
的情况下,应将正确的UTF-8传输到服务器。
您后来更改了客户端以传入UTF8编码的AnsiString
,但是您将DefAnsiEncoding
设置为IndyTextEncoding_8Bit
,因此AnsiString
不能正确地转换为Unicode,因此后续到UTF-8的转换是错误的。在这种情况下,需要将DefAnsiEncoding
设置为IndyTextEncoding_UTF8
。我建议将DefAnsiEncoding
设置为IndyTextEncoding_OSDefault
,并使用操作系统区域设置编码的AnsiString
s,除非您有令人信服的理由不这样做。
在服务器端
只有当客户端发出OPTS UTF-8 <NLST>
或OPTS UTF8 ON
命令时,TIdFTPServer
才会自动将IOHandler的DefStringEncoding
切换到IndyTextEncoding_UTF8
( TIdFTP
会这样做,但其他客户端可能不会)。请注意,这不符合RFC 2640,Indy还没有完全实现它。
您将DefStringEncoding
和DefAnsiEncoding
都设置为IndyTextEncoding_UTF8
,这在很大程度上是正常的。在公开对AnsiString
的访问的事件中,您最终会得到UTF8编码的AnsiString
。
但是,在您的OnRetrieveFile
版本中,Delphi事件使用WideString
作为其AFileName
参数。在服务器将文件名作为AnsiString
从套接字读取后,它将按原样传递给事件处理程序,使用RTL自己的转换逻辑将其转换为WideString
,该逻辑默认使用操作系统的区域设置。因此,在您的情况下,AnsiString
是UTF8编码的,但操作系统区域设置是波兰语,您将最终得到格式错误的转换。幸运的是,您可以通过预先调用RTL的SetMultiByteConversionCodePage()
函数来使用代码页65001 (UTF-8)执行AnsiString
<->WideString
转换来缓解这个问题。
然而,TIdFTPServer
的几个事件在Delphi2007和更早版本中使用WideString
参数,因此TIdFTPServer
内部有相当多的区域依赖于AnsiString
的默认UTF<->WideString
转换。所以,我建议在服务器端也将DefAnsiEncoding
设置为IndyTextEncoding_OSDefault
,除非您有令人信服的理由不这样做。如果您想强制DefStringEncoding
到IndyTextEncoding_UTF8
,而不管OPTS
命令,这是可以的(前提是您的客户端只发送UTF8编码路径)。
说到这里,试试这个:
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.
发布于 2021-04-25 07:40:11
最后,我放弃了在D7下使用杂乱的TextEncoding。迷失了三天,我还在同样的地方。
长期的调查让我选择了TIdASCIIEncoding.GetBytes()
,无论我选择哪种编码类型。这里,任何高于$007F (127)的字符都被转换为"?“这对于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);
虽然丑陋但很有效..。
https://stackoverflow.com/questions/67231458
复制相似问题