Delphi error in Indy FTP List Method?

I try to create a list of files matching a specific file mask, and Indy crashes with this error

EidReplyRFCError with the message '.': There is no such file or directory.

I tried several options and this is the result:

FTP.List( aFiles, '', true ); => it works

FTP.List( aFiles, '*.*', false ); => It works too

FTP.List( aFiles, '*.*', true ); => it does not work

FTP.List( aFiles, '*.zip', true ); => this also fails (despite the fact that this is an example in the latest documentation)

FTP.List( '*.*', false ); => it works

FTP.List( '*.*', true ); => it does not work

I am using Delphi XE5 and Indy version 10.6. The same problem exists in XE8, if necessary.

Maybe the functionality has changed, and now the documentation is incorrect or an error in Indy?

I need “details”, so I can compare timestamps and sizes.

+7
delphi ftp indy
source share
2 answers

This is not an error in TIdFTP . This is more of an omission in the Indy documentation.

EIdReplyRFCError means that the FTP server itself reports an error in response to the command that TIdFTP.List() sends. Depending on the values ​​of the ADetails and TIdFTP UseMLIS + CanUseMLS , List() can send one of three different commands:

 ADetails=False: NLST [ASpecifier] ADetails=True: TIdFTP.UseMLIS=True and TIdFTP.CanUseMLS=True: MLSD [ASpecifier] TIdFTP.UseMLIS=False or TIdFTP.CanUseMLS=False: LIST [ASpecifier] 

Thus:

 FTP.List( aFiles, '', true ); // this works // sends either 'LIST' or 'MLSD' FTP.List( aFiles, '*.*', false ); // this works too // sends 'NLST *.*' FTP.List( aFiles, '*.*', true ); // this fails // sends either 'LIST *.*' or 'MLSD *.*' FTP.List( aFiles, '*.zip', true ); // this fails too // sends either 'LIST *.zip' or 'MLSD *.zip' FTP.List( '*.*', false ); // this works // sends 'NLST *.*' FTP.List( '*.*', true ); // this fails // sends either 'LIST *.*' or 'MLSD *.*' 

Note that all failed commands have something in common that can send the MLSD ASpecifier .

Per RFC 959 , which defines the LIST and NLST commands:

LIST (LIST)

This command causes the list to be sent from the server to passive DTP. If the path name indicates a directory or another group of files, the server must transfer a list of files in the specified directory. If the path name indicates the file, then the server should send the current information to the file. A null argument implies that the current user is running or the default directory. ...

LIST NAME (NLST)

This command sends a directory listing from the server to the user. The path must specify a directory or other system filegroup descriptor; null indicates the current directory. ...

Per RFC 3659 , which defines the MLSD command:

The MLST and MLSD commands allow one optional argument to be resolved. This argument can be either a directory name or just for the MLST file name. For these purposes, “file name” is the name of any object on the NVFS server that is not a directory. Where TVFS is supported by any relative TVFS path existing in the current working directory or any fully qualified TVFS channel name. If a directory name is specified then MLSD should return a list of the contents of the named directory; otherwise, it returns 501 and does not open a data connection. ...

If no argument is specified, MLSD should return a list of the contents of the current working directory , and MLST should return a list containing information about the current working directory itself ....

...

If the FTP client sends an invalid argument, the FTP server MUST respond with error code 501.

*.* and *.zip are not directory names, so the server will fail if TIdFTP.List() sends the MLSD *.* or MLSD *.zip . So it’s reasonable that TIdFTP.UseMLIS and TIdFTP.CanUseMLS most likely True in your case ( UseMLIS is True by default, and CanUseMLS usually used on modern FTP servers).

The MLSD command MLSD not support server-side filtering, as the LIST / NLST commands do. Therefore, you cannot use things like *.* And *.zip with MLSD . You will need to get a complete list of directories and then ignore any entries that you are not interested in. Otherwise, set TIdFTP.UseMLIS to False before calling TIdFTP.List() , but then you risk TIdFTP.DirectoryListing incorrectly TIdFTP.DirectoryListing list of directories on some servers, since the format used by the LIST command has never been standardized, and there are hundreds of custom formats. used all over the Internet (and why TIdFTP in Indy 10 includes dozens of parsing listings when LIST used). Unlike MLSx , which has a standardized format (therefore it was introduced primarily to replace the disadvantages of LIST ).

Thus, it all boils down to the fact that - when TIdFTP.UseMLIS and TIdFTP.CanUseMLS both True, the ASpecifier MUST be empty or a directory, NOT a file mask.

The TIdFTP.List() documentation states that List() can internally call TIdFTP.ExtListDir() to send the MLSD command, but in this case this restriction is not specifically mentioned in the ASpecifier parameter:

If CanUseMLS contains True, ExtListDir is called to capture and store the results of the FTP MLSD command in the ADest parameter variable instead of the LIST or NLST commands . No further processing is performed in the list method under this circumstance, and the method ends.

When ADetails is False, only the file or directory name is returned in the ADest line list using the FTP NLST command . When ADetails is True, the List can return FTP server-specific data, including file size, modification date, and permissions for the owner, group, and user using the FTP LIST command.

The TIdFTP.ExtListDir() documentation states that its input parameter must be a directory name:

The MLSD command supported by ExtListDir accepts an optional directory name or relative path in the Adirectory for the directory listing. If an empty string is passed to ADirectory, the current directory is used for directory operations.

On the other hand: the TIdFTP.DirFormat property will tell you which list format was found after TIdFTP.DirectoryListing parsed the results. Or you can look at the Details and UsedMLS TListFTP.ListResult (type TIdFTPListResult to access the properties) to see which command was sent to TIdFTP.List() (if it was successful).

+11
source share

An alternative solution is to enable

IdAllFTPListParsers

In your suggestion uses and disable UseMLIS .

Same:

 uses .... IdAllFTPListParsers; ..... procedure TForm1.DoThis; var i: integer; begin if not IDFTP1.Connected then IDFTP1.Connect; IDFTP1.UseMLIS:= false; IDFTP1.List; for i:= 0 to IDFTP1.DirectoryListing.Count -1 do begin .. process directory items. IdFTP1.TransferType:= ftBinary; ..Get your files 
0
source share

All Articles