首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Delphi7中的Indy 10.6.2 idFTP -接收文件时文件名中的本机符号问题-可能是DefStringEncoding的不良行为

Delphi7中的Indy 10.6.2 idFTP -接收文件时文件名中的本机符号问题-可能是DefStringEncoding的不良行为
EN

Stack Overflow用户
提问于 2021-04-23 22:08:06
回答 2查看 411关注 0票数 3

我正在使用Indy 10.6.2和Delphi 7以及64位Windows 10和波兰语。

我运行一个FTP服务器:FTPServer: TIdFTPServer,通过使用FTPClient: TIdFTP,我试图获得一个在其文件名中包含国家符号的文件。不幸的是,在调用Get()函数后,在OnRetrieveFile事件中的服务器端,而不是AFileName中的国家符号,我以问号结束,这显然会导致其他异常。

为了进行测试,我在同一台机器和同一个应用程序中同时运行服务器和客户端,以消除任何其他干扰。

在客户端,我已经尝试过了:

代码语言:javascript
运行
复制
 FTPClient.DefStringEncoding := IndyTextEncoding_UTF8;
 FTPClient.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
 FTPClient.IOHandler.DefAnsiEncoding   := IndyTextEncoding_UTF8;

在服务器端,我尝试了:

代码语言:javascript
运行
复制
 ASender.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
 ASender.Connection.IOHandler.DefAnsiEncoding   := IndyTextEncoding_UTF8;

这些都不会有什么不同。我还尝试了其他编码,但也失败了。

下面是我的测试应用程序,以及客户机和服务器的文本DFM值。

代码语言:javascript
运行
复制
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部分:

代码语言:javascript
运行
复制
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。标题只是为了快速调试)

代码语言:javascript
运行
复制
...
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;
EN

回答 2

Stack Overflow用户

发布于 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。

默认情况下,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传输到服务器。

您后来更改了客户端以传入UTF8编码的AnsiString,但是您将DefAnsiEncoding设置为IndyTextEncoding_8Bit,因此AnsiString不能正确地转换为Unicode,因此后续到UTF-8的转换是错误的。在这种情况下,需要将DefAnsiEncoding设置为IndyTextEncoding_UTF8。我建议将DefAnsiEncoding设置为IndyTextEncoding_OSDefault,并使用操作系统区域设置编码的AnsiStrings,除非您有令人信服的理由不这样做。

在服务器端

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

您将DefStringEncodingDefAnsiEncoding都设置为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,除非您有令人信服的理由不这样做。如果您想强制DefStringEncodingIndyTextEncoding_UTF8,而不管OPTS命令,这是可以的(前提是您的客户端只发送UTF8编码路径)。

说到这里,试试这个:

代码语言:javascript
运行
复制
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.

或者这样:

代码语言:javascript
运行
复制
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.
票数 1
EN

Stack Overflow用户

发布于 2021-04-25 07:40:11

最后,我放弃了在D7下使用杂乱的TextEncoding。迷失了三天,我还在同样的地方。

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

我将使用更长的路来解决这个问题,只要我只在我的两个应用程序之间进行通信就可以了。我知道它正在打补丁,但至少可以工作。

“解决方案”:

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

客户端:

代码语言:javascript
运行
复制
S := IdEncoderMIME1.EncodeString('sample ąęśćłó',IndyTextEncoding_UTF8,IndyTextEncoding_UTF8);
FTPClient.Get(S,'C:\węzły.cpy',True,False);

服务器端:

代码语言:javascript
运行
复制
S := Copy(AFileName,2,Length(AFileName));
S := IdDecoderMIME1.DecodeString(S,IndyTextEncoding_UTF8,IndyTextEncoding_UTF8);

虽然丑陋但很有效..。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67231458

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档