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

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中使用国家符号,而是使用问号,这显然会导致其他异常 对于测试,我在同一台机器上和同一个应用程序中同时运行服务器和客户端,以消除任何其他干扰 在客户端,

我正在使用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.
背景 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 encoded
ansisting
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);