restemplate/indy/Protocols/IdFTP.pas

4347 lines
150 KiB
Plaintext
Raw Permalink Blame History

{
$Project$
$Workfile$
$Revision$
$DateUTC$
$Id$
This file is part of the Indy (Internet Direct) project, and is offered
under the dual-licensing agreement described on the Indy website.
(http://www.indyproject.org/)
Copyright:
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
$Log$
}
{
Rev 1.126 4/28/2005 BTaylor
Changed .Size to use Int64
Rev 1.125 4/15/2005 9:10:10 AM JPMugaas
Changed the default timeout in TIdFTP to one minute and made a comment about
this.
Some firewalls don't handle control connections properly during long data
transfers. They will timeout the control connection because it's idle and
making it worse is that they will chop off a connection instead of closing it
causing TIdFTP to wait forever for nothing.
Rev 1.124 3/20/2005 10:42:44 PM JPMugaas
Marked TIdFTP.Quit as deprecated. We need to keep it only for compatibility.
Rev 1.123 3/20/2005 2:44:08 PM JPMugaas
Should now send quit. Verified here.
Rev 1.122 3/12/2005 6:57:12 PM JPMugaas
Attempt to add ACCT support for firewalls. I also used some logic from some
WS-FTP Pro about ACCT to be more consistant with those Firescripts.
Rev 1.121 3/10/2005 2:41:12 PM JPMugaas
Removed the UseTelnetAbort property. It turns out that sending the sequence
is causing problems on a few servers. I have made a comment about this in
the source-code so someone later on will know why I decided not to send
those.
Rev 1.120 3/9/2005 10:05:54 PM JPMugaas
Minor changes for Indy conventions.
Rev 1.119 3/9/2005 9:15:46 PM JPMugaas
Changes submitted by Craig Peterson, Scooter Software He noted this:
"We had a user who's FTP server prompted for account info after a
regular login, so I had to add an explicit Account string property and
an OnNeedAccount event that we could use for a prompt." This does break any
code using TIdFTP.Account.
TODO: See about integrating Account Info into the proxy login sequences.
Rev 1.118 3/9/2005 10:40:16 AM JPMugaas
Made comment explaining why I had made a workaround in a procedure.
Rev 1.117 3/9/2005 10:28:32 AM JPMugaas
Fix for Abort problem when uploading. A workaround I made for WS-FTP Pro
Server was not done correctly.
Rev 1.116 3/9/2005 1:21:38 AM JPMugaas
Made refinement to Abort and the data transfers to follow what Kudzu had
originally done in Indy 8. I also fixed a problem with ABOR at
ftp.ipswitch.com and I fixed a regression at ftp.marist.edu that occured when
getting a directory.
Rev 1.115 3/8/2005 12:14:50 PM JPMugaas
Renamed UseOOBAbort to UseTelnetAbort because that's more accurate. We still
don't support Out of Band Data (hopefully, we'll never have to do that).
Rev 1.114 3/7/2005 10:40:10 PM JPMugaas
Improvements:
1) Removed some duplicate code.
2) ABOR should now be properly handled outside of a data operation.
3) I added a UseOOBAbort read-write public property for controlling how the
ABOR command is sent. If true, the Telnet sequences are sent or if false,
the ABOR without sequences is sent. This is set to false by default because
one FTP client (SmartFTP recently removed the Telnet sequences from their
program).
This code is expiriemental.
Rev 1.113 3/7/2005 5:46:34 PM JPMugaas
Reworked FTP Abort code to make it more threadsafe and make abort work. This
is PRELIMINARY.
Rev 1.112 3/5/2005 3:33:56 PM JPMugaas
Fix for some compiler warnings having to do with TStream.Read being platform
specific. This was fixed by changing the Compressor API to use TIdStreamVCL
instead of TStream. I also made appropriate adjustments to other units for
this.
Rev 1.111 2/24/2005 6:46:36 AM JPMugaas
Clarrified remarks I made and added a few more comments about syntax in
particular cases in the set modified file date procedures.
That's really been a ball....NOT!!!!
Rev 1.110 2/24/2005 6:25:08 AM JPMugaas
Attempt to fix problem setting Date with Titan FTP Server. I had made an
incorrect assumption about MDTM on that system. It uses Syntax 3 (see my
earlier note above the File Date Set problem.
Rev 1.109 2/23/2005 6:32:54 PM JPMugaas
Made note about MDTM syntax inconsistancy. There's a discussion about it.
Rev 1.108 2/12/2005 8:08:04 AM JPMugaas
Attempt to fix MDTM bug where msec was being sent.
Rev 1.107 1/12/2005 11:26:44 AM JPMugaas
Memory Leak fix when processing MLSD output and some minor tweeks Remy had
E-Mailed me last night.
Rev 1.106 11/18/2004 2:39:32 PM JPMugaas
Support for another FTP Proxy type.
Rev 1.105 11/18/2004 12:18:50 AM JPMugaas
Fixed compile error.
Rev 1.104 11/17/2004 3:59:22 PM JPMugaas
Fixed a TODO item about FTP Proxy support with a "Transparent" proxy. I
think you connect to the regular host and the firewall will intercept its
login information.
Rev 1.103 11/16/2004 7:31:52 AM JPMugaas
Made a comment noting that UserSite is the same as USER after login for later
reference.
Rev 1.102 11/5/2004 1:54:42 AM JPMugaas
Minor adjustment - should not detect TitanFTPD better (tested at:
ftp.southrivertech.com).
If MLSD is being used, SITE ZONE will not be issued. It's not needed because
the MLSD spec indicates the time is based on GMT.
Rev 1.101 10/27/2004 12:58:08 AM JPMugaas
Improvement from Tobias Giesen http://www.superflexible.com
His notation is below:
"here's a fix for TIdFTP.IndexOfFeatLine. It does not work the
way it is used in TIdFTP.SetModTime, because it only
compares the first word of the FeatLine."
Rev 1.100 10/26/2004 9:19:10 PM JPMugaas
Fixed references.
Rev 1.99 9/16/2004 3:24:04 AM JPMugaas
TIdFTP now compresses to the IOHandler and decompresses from the IOHandler.
Noted some that the ZLib code is based was taken from ZLibEx.
Rev 1.98 9/13/2004 12:15:42 AM JPMugaas
Now should be able to handle some values better as suggested by Michael J.
Leave.
Rev 1.97 9/11/2004 10:58:06 AM JPMugaas
FTP now decompresses output directly to the IOHandler.
Rev 1.96 9/10/2004 7:37:42 PM JPMugaas
Fixed a bug. We needed to set Passthrough instead of calling StartSSL. This
was causing a SSL problem with upload.
Rev 1.95 8/2/04 5:56:16 PM RLebeau
Tweaks to TIdFTP.InitDataChannel()
Rev 1.94 7/30/2004 1:55:04 AM DSiders
Corrected DoOnRetrievedDir naming.
Rev 1.93 7/30/2004 12:36:32 AM DSiders
Corrected spelling in OnRetrievedDir, DoOnRetrievedDir declarations.
Rev 1.92 7/29/2004 2:15:28 AM JPMugaas
New property for controlling what AUTH command is sent. Fixed some minor
issues with FTP properties. Some were not set to defaults causing
unpredictable results -- OOPS!!!
Rev 1.91 7/29/2004 12:04:40 AM JPMugaas
New events for Get and Put as suggested by Don Sides and to complement an
event done by APR.
Rev 1.90 7/28/2004 10:16:14 AM JPMugaas
New events for determining when a listing is finished and when the dir
parsing begins and ends. Dir parsing is done sometimes when DirectoryListing
is referenced.
Rev 1.89 7/27/2004 2:03:54 AM JPMugaas
New property:
ExternalIP - used to specify an IP address for the PORT and EPRT commands.
This should be blank unless you are behind a NAT and you need to use PORT
transfers with SSL. You would set ExternalIP to the NAT's IP address on the
Internet.
The idea is this:
1) You set up your NAT to forward a range ports ports to your computer behind
the NAT.
2) You specify that a port range with the DataPortMin and DataPortMin
properties.
3) You set ExternalIP to the NAT's Internet IP address.
I have verified this with Indy and WS FTP Pro behind a NAT router.
Rev 1.88 7/23/04 7:09:50 PM RLebeau
Bug fix for TFileStream access rights in Get()
Rev 1.87 7/18/2004 3:00:12 PM DSiders
Added localization comments.
Rev 1.86 7/16/2004 4:28:40 AM JPMugaas
CCC Support in TIdFTP to complement that capability in TIdFTPServer.
Rev 1.85 7/13/04 6:48:14 PM RLebeau
Added support for new DataPort and DataPortMin/Max properties
Rev 1.84 7/6/2004 4:51:46 PM DSiders
Corrected spelling of Challenge in properties, methods, types.
Rev 1.83 7/3/2004 3:15:50 AM JPMugaas
Checked in so everyone else can work on stuff while I'm away.
Rev 1.82 6/27/2004 1:45:38 AM JPMugaas
Can now optionally support LastAccessTime like Smartftp's FTP Server could.
I also made the MLST listing object and parser support this as well.
Rev 1.81 6/20/2004 8:31:58 PM JPMugaas
New events for reporting greeting and after login banners during the login
sequence.
Rev 1.80 6/20/2004 6:56:42 PM JPMugaas
Start oin attempt to support FXP with Deflate compression. More work will
need to be done.
Rev 1.79 6/17/2004 3:42:32 PM JPMugaas
Adjusted code for removal of dmBlock and dmCompressed. Made TransferMode a
property. Note that the Set method is odd because I am trying to keep
compatibility with older Indy versions.
Rev 1.78 6/14/2004 6:19:02 PM JPMugaas
This now refers to TIdStreamVCL when downloading isntead of directly to a
memory stream when compressing data.
Rev 1.77 6/14/2004 8:34:52 AM JPMugaas
Fix for AV on Put with Passive := True.
Rev 1.76 6/11/2004 9:34:12 AM DSiders
Added "Do not Localize" comments.
Rev 1.75 2004.05.20 11:37:16 AM czhower
IdStreamVCL
Rev 1.74 5/6/2004 6:54:26 PM JPMugaas
FTP Port transfers with TransparentProxies is enabled. This only works if
the TransparentProxy supports a "bind" request.
Rev 1.73 5/4/2004 11:16:28 AM JPMugaas
TransferTimeout property added and enabled (Bug 96).
Rev 1.72 5/4/2004 11:07:12 AM JPMugaas
Timeouts should now be reenabled in TIdFTP.
Rev 1.71 4/19/2004 5:05:02 PM JPMugaas
Class rework Kudzu wanted.
Rev 1.70 2004.04.16 9:31:42 PM czhower
Remove unnecessary duplicate string parsing and replaced with .assign.
Rev 1.69 2004.04.15 7:09:04 PM czhower
.NET overloads
Rev 1.68 4/15/2004 9:46:48 AM JPMugaas
List no longer requires a TStrings. It turns out that it was an optional
parameter.
Rev 1.67 2004.04.15 2:03:28 PM czhower
Removed login param from connect and made it a prop like POP3.
Rev 1.66 3/3/2004 5:57:40 AM JPMugaas
Some IFDEF excluses were removed because the functionality is now in DotNET.
Rev 1.65 2004.03.03 11:54:26 AM czhower
IdStream change
Rev 1.64 2/20/2004 1:01:06 PM JPMugaas
Preliminary FTP PRET command support for using PASV with a distributed FTP
server (Distributed PASV -
http://drftpd.org/wiki/wiki.phtml?title=Distributed_PASV).
Rev 1.63 2/17/2004 12:25:52 PM JPMugaas
The client now supports MODE Z (deflate) uploads and downloads as specified
by http://www.ietf.org/internet-drafts/draft-preston-ftpext-deflate-00.txt
Rev 1.62 2004.02.03 5:45:10 PM czhower
Name changes
Rev 1.61 2004.02.03 2:12:06 PM czhower
$I path change
Rev 1.60 1/27/2004 10:17:10 PM JPMugaas
Fix from Steve Loft for a server that sends something like this:
"227 Passive mode OK (195,92,195,164,4,99 )"
Rev 1.59 1/27/2004 3:59:28 PM SPerry
StringStream ->IdStringStream
Rev 1.58 24/01/2004 19:13:58 CCostelloe
Cleaned up warnings
Rev 1.57 1/21/2004 2:27:50 PM JPMugaas
Bullete Proof FTPD and Titan FTP support SITE ZONE. Saw this in a command
database in StaffFTP.
InitComponent.
Rev 1.56 1/19/2004 9:05:38 PM JPMugaas
Fixes to FTP Set Date functionality.
Introduced properties for Time Zone information from the server. The way it
works is this, if TIdFTP detects you are using "Serv-U" or SITE ZONE is
listed in the FEAT reply, Indy obtains the time zone information with the
SITE ZONE command and makes the appropriate calculation. Indy then uses this
information to calculate a timestamp to send to the server with the MDTM
command. You can also use the Time Zone information yourself to convert the
FTP directory listing item timestamps into GMT and than convert that to your
local time.
FTP Voyager uses SITE ZONE as I've described.
Rev 1.55 1/19/2004 4:39:08 AM JPMugaas
You can now set the time for a file on the server. Note that these methods
try to treat the time as relative to GMT.
Rev 1.54 1/17/2004 9:09:30 PM JPMugaas
Should now compile.
Rev 1.53 1/17/2004 7:48:02 PM JPMugaas
FXP site to site transfer code was redone for improvements with FXP with TLS.
It actually works and I verified with RaidenFTPD
(http://www.raidenftpd.com/) and the Indy FTP server components. I also
lowered the requirements for TLS FXP transfers. The requirements now are:
1) Only server (either the recipient or the sendor) has to support SSCN
or
2) The server receiving a PASV must support CPSV and the transfer is done
with IPv4.
Rev 1.52 1/9/2004 2:51:26 PM JPMugaas
Started IPv6 support.
Rev 1.51 11/27/2003 4:55:28 AM JPMugaas
Made STOU functionality separate from PUT functionality. Put now requires a
destination filename except where a source-file name is given. In that case,
the default is the filename from the source string.
Rev 1.50 10/26/2003 04:28:50 PM JPMugaas
Reworked Status.
The old one was problematic because it assumed that STAT was a request to
send a directory listing through the control channel. This assumption is not
correct. It provides a way to get a freeform status report from a server.
With a Path parameter, it should work like a LIST command except that the
control connection is used. We don't support that feature and you should use
our LIst method to get the directory listing anyway, IMAO.
Rev 1.49 10/26/2003 9:17:46 PM BGooijen
Compiles in DotNet, and partially works there
Rev 1.48 10/24/2003 12:43:48 PM JPMugaas
Should work again.
Rev 1.47 2003.10.24 10:43:04 AM czhower
TIdSTream to dos
Rev 1.46 10/20/2003 03:06:10 PM JPMugaas
SHould now work.
Rev 1.45 10/20/2003 01:00:38 PM JPMugaas
EIdException no longer raised. Some things were being gutted needlessly.
Rev 1.44 10/19/2003 12:58:20 PM DSiders
Added localization comments.
Rev 1.43 2003.10.14 9:56:50 PM czhower
Compile todos
Rev 1.42 2003.10.12 3:50:40 PM czhower
Compile todos
Rev 1.41 10/10/2003 11:32:26 PM SPerry
-
Rev 1.40 10/9/2003 10:17:02 AM JPMugaas
Added overload for GetLoginPassword for providing a challanage string which
doesn't have to the last command reply.
Added CLNT support.
Rev 1.39 10/7/2003 05:46:20 AM JPMugaas
SSCN Support added.
Rev 1.38 10/6/2003 08:56:44 PM JPMugaas
Reworked the FTP list parsing framework so that the user can obtain the list
of capabilities from a parser class with TIdFTP. This should permit the user
to present a directory listing differently for each parser (some FTP list
parsers do have different capabilities).
Rev 1.37 10/1/2003 12:51:18 AM JPMugaas
SSL with active (PORT) transfers now should work again.
Rev 1.36 9/30/2003 09:50:38 PM JPMugaas
FTP with TLS should work better. It turned out that we were negotiating it
several times causing a hang. I also made sure that we send PBSZ 0 and PROT
P for both implicit and explicit TLS. Data ports should work in PASV again.
Rev 1.35 9/28/2003 11:41:06 PM JPMugaas
Reworked Eldos's proposed FTP fix as suggested by Henrick Hellstr<74>m by moving
all of the IOHandler creation code to InitDataChannel. This should reduce
the likelihood of error.
Rev 1.33 9/18/2003 11:22:40 AM JPMugaas
Removed a temporary workaround for an OnWork bug that was in the Indy Core.
That bug was fixed so there's no sense in keeping a workaround here.
Rev 1.32 9/12/2003 08:05:30 PM JPMugaas
A temporary fix for OnWork events not firing. The bug is that OnWork events
aren't used in IOHandler where ReadStream really is located.
Rev 1.31 9/8/2003 02:33:00 AM JPMugaas
OnCustomFTPProxy added to allow Indy to support custom FTP proxies. When
using this event, you are responsible for programming the FTP Proxy and FTP
Server login sequence.
GetLoginPassword method function for returning the password used when logging
into a FTP server which handles OTP calculation. This way, custom firewall
support can handle One-Time-Password system transparently. You do have to
send the User ID before calling this function because the OTP challenge is
part of the reply.
Rev 1.30 6/10/2003 11:10:00 PM JPMugaas
Made comments about our loop that tries several AUTH command variations.
Some servers may only accept AUTH SSL while other servers only accept AUTH
TLS.
Rev 1.29 5/26/2003 12:21:54 PM JPMugaas
Rev 1.28 5/25/2003 03:54:20 AM JPMugaas
Rev 1.27 5/19/2003 08:11:32 PM JPMugaas
Now should compile properly with new code in Core.
Rev 1.26 5/8/2003 11:27:42 AM JPMugaas
Moved feature negoation properties down to the ExplicitTLSClient level as
feature negotiation goes hand in hand with explicit TLS support.
Rev 1.25 4/5/2003 02:06:34 PM JPMugaas
TLS handshake itself can now be handled.
Rev 1.24 4/4/2003 8:01:32 PM BGooijen
now creates iohandler for dataconnection
Rev 1.23 3/31/2003 08:40:18 AM JPMugaas
Fixed problem with QUIT command.
Rev 1.22 3/27/2003 3:41:28 PM BGooijen
Changed because some properties are moved to IOHandler
Rev 1.21 3/27/2003 05:46:24 AM JPMugaas
Updated framework with an event if the TLS negotiation command fails.
Cleaned up some duplicate code in the clients.
Rev 1.20 3/26/2003 04:19:20 PM JPMugaas
Cleaned-up some code and illiminated some duplicate things.
Rev 1.19 3/24/2003 04:56:10 AM JPMugaas
A typecast was incorrect and could cause a potential source of instability if
a TIdIOHandlerStack was not used.
Rev 1.18 3/16/2003 06:09:58 PM JPMugaas
Fixed port setting bug.
Rev 1.17 3/16/2003 02:40:16 PM JPMugaas
FTP client with new design.
Rev 1.16 3/16/2003 1:02:44 AM BGooijen
Added 2 events to give the user more control to the dataconnection, moved
SendTransferType, enabled ssl
Rev 1.15 3/13/2003 09:48:58 AM JPMugaas
Now uses an abstract SSL base class instead of OpenSSL so 3rd-party vendors
can plug-in their products.
Rev 1.14 3/7/2003 11:51:52 AM JPMugaas
Fixed a writeln bug and an IOError issue.
Rev 1.13 3/3/2003 07:06:26 PM JPMugaas
FFreeIOHandlerOnDisconnect to FreeIOHandlerOnDisconnect at Bas's instruction
Rev 1.12 2/21/2003 06:54:36 PM JPMugaas
The FTP list processing has been restructured so that Directory output is not
done by IdFTPList. This now also uses the IdFTPListParserBase for parsing so
that the code is more scalable.
Rev 1.11 2/17/2003 04:45:36 PM JPMugaas
Now temporarily change the transfer mode to ASCII when requesting a DIR.
TOPS20 does not like transfering dirs in binary mode and it might be a good
idea to do it anyway.
Rev 1.10 2/16/2003 03:22:20 PM JPMugaas
Removed the Data Connection assurance stuff. We figure things out from the
draft specificaiton, the only servers we found would not send any data after
the new commands were sent, and there were only 2 server types that supported
it anyway.
Rev 1.9 2/16/2003 10:51:08 AM JPMugaas
Attempt to implement:
http://www.ietf.org/internet-drafts/draft-ietf-ftpext-data-connection-assuranc
e-00.txt
Currently commented out because it does not work.
Rev 1.8 2/14/2003 11:40:16 AM JPMugaas
Fixed compile error.
Rev 1.7 2/14/2003 10:38:32 AM JPMugaas
Removed a problematic override for GetInternelResponse. It was messing up
processing of the FEAT.
Rev 1.6 12-16-2002 20:48:10 BGooijen
now uses TIdIOHandler.ConstructIOHandler to construct iohandlers
IPv6 works again
Independant of TIdIOHandlerStack again
Rev 1.5 12-15-2002 23:27:26 BGooijen
now compiles on Indy 10, but some things like IPVersion still need to be
changed
Rev 1.4 12/15/2002 04:07:02 PM JPMugaas
Started port to Indy 10. Still can not complete it though.
Rev 1.3 12/6/2002 05:29:38 PM JPMugaas
Now decend from TIdTCPClientCustom instead of TIdTCPClient.
Rev 1.2 12/1/2002 04:18:02 PM JPMugaas
Moved all dir parsing code to one place. Reworked to use more than one line
for determining dir format type along with flfNextLine dir format type.
Rev 1.1 11/14/2002 04:02:58 PM JPMugaas
Removed cludgy code that was a workaround for the RFC Reply limitation. That
is no longer limited.
Rev 1.0 11/14/2002 02:20:00 PM JPMugaas
2002-10-25 - J. Peter Mugaas
- added XCRC support - specified by "GlobalSCAPE Secure FTP Server User<65>s Guide"
which is available at http://www.globalscape.com
and also explained at http://www.southrivertech.com/support/titanftp/webhelp/titanftp.htm
- added COMB support - specified by "GlobalSCAPE Secure FTP Server User<65>s Guide"
which is available at http://www.globalscape.com
and also explained at http://www.southrivertech.com/support/titanftp/webhelp/titanftp.htm
2002-10-24 - J. Peter Mugaas
- now supports RFC 2640 - FTP Internalization
2002-09-18
_ added AFromBeginning parameter to InternalPut to correctly honor the AAppend parameter of Put
2002-09-05 - J. Peter Mugaas
- now complies with RFC 2389 - Feature negotiation mechanism for the File Transfer Protocol
- now complies with RFC 2428 - FTP Extensions for IPv6 and NATs
2002-08-27 - Andrew P.Rybin
- proxy support fix (non-standard ftp port's)
2002-01-xx - Andrew P.Rybin
- Proxy support, OnAfterGet (ex:decrypt, set srv timestamp)
- J.Peter Mugaas: not readonly ProxySettings
A Neillans - 10/17/2001
Merged changes submitted by Andrew P.Rybin
Correct command case problems - some servers expect commands in Uppercase only.
SP - 06/08/2001
Added a few more functions
Doychin - 02/18/2001
OnAfterLogin event handler and Login method
OnAfterLogin is executed after successfull login but before setting up the
connection properties. This event can be used to provide FTP proxy support
from the user application. Look at the FTP demo program for more information
on how to provide such support.
Doychin - 02/17/2001
New onFTPStatus event
New Quote method for executing commands not implemented by the compoent
-CleanDir contributed by Amedeo Lanza
}
unit IdFTP;
{
TODO: Change the FTP demo to demonstrate the use of the new events and add proxy support
}
interface
{$i IdCompilerDefines.inc}
uses
Classes,
IdAssignedNumbers, IdGlobal, IdCustomTransparentProxy, IdExceptionCore,
IdExplicitTLSClientServerBase, IdFTPCommon, IdFTPList, IdFTPListParseBase,
IdException, IdIOHandler, IdIOHandlerSocket, IdReplyFTP, IdBaseComponent,
IdReplyRFC, IdReply, IdSocketHandle, IdTCPConnection, IdTCPClient,
IdThreadSafe, IdZLibCompressorBase;
type
//APR 011216:
TIdFtpProxyType = (
fpcmNone,//Connect method:
fpcmUserSite, //Send command USER user@hostname - USER after login (see: http://isservices.tcd.ie/internet/command_config.php)
fpcmSite, //Send command SITE (with logon)
fpcmOpen, //Send command OPEN
fpcmUserPass,//USER user@firewalluser@hostname / PASS pass@firewallpass
fpcmTransparent, //First use the USER and PASS command with the firewall username and password, and then with the target host username and password.
fpcmUserHostFireWallID, //USER hostuserId@hostname firewallUsername
fpcmNovellBorder, //Novell BorderManager Proxy
fpcmHttpProxyWithFtp, //HTTP Proxy with FTP support. Will be supported in Indy 10
fpcmCustomProxy // use OnCustomFTPProxy to customize the proxy login
); //TIdFtpProxyType
//This has to be in the same order as TLS_AUTH_NAMES
TAuthCmd = (tAuto, tAuthTLS, tAuthSSL, tAuthTLSC, tAuthTLSP);
const
Id_TIdFTP_TransferType = {ftBinary} ftASCII; // RLebeau 1/22/08: per RFC 959
Id_TIdFTP_Passive = False;
Id_TIdFTP_UseNATFastTrack = False;
Id_TIdFTP_HostPortDelimiter = ':';
Id_TIdFTP_DataConAssurance = False;
Id_TIdFTP_DataPortProtection = ftpdpsClear;
//
DEF_Id_TIdFTP_Implicit = False;
DEF_Id_FTP_UseExtendedDataPort = False;
DEF_Id_TIdFTP_UseExtendedData = False;
DEF_Id_TIdFTP_UseMIS = True;
DEF_Id_FTP_UseCCC = False;
DEF_Id_FTP_AUTH_CMD = tAuto;
DEF_Id_FTP_ListenTimeout = 10000; // ten seconds
{
Soem firewalls don't handle control connections properly during long data transfers.
They will timeout the control connection because it's idle and making it worse is that they
will chop off a connection instead of closing it causing TIdFTP to wait forever for nothing.
}
DEF_Id_FTP_READTIMEOUT = 60000; //one minute
DEF_Id_FTP_UseHOST = True;
DEF_Id_FTP_PassiveUseControlHost = False;
DEF_Id_FTP_AutoIssueFEAT = True;
DEF_Id_FTP_AutoLogin = True;
type
//Added by SP
TIdCreateFTPList = procedure(ASender: TObject; var VFTPList: TIdFTPListItems) of object;
//TIdCheckListFormat = procedure(ASender: TObject; const ALine: String; var VListFormat: TIdFTPListFormat) of object;
TOnAfterClientLogin = TNotifyEvent;
TIdFtpAfterGet = procedure(ASender: TObject; AStream: TStream) of object; //APR
TIdOnDataChannelCreate = procedure(ASender: TObject; ADataChannel: TIdTCPConnection) of object;
TIdOnDataChannelDestroy = procedure(ASender: TObject; ADataChannel: TIdTCPConnection) of object;
TIdNeedAccountEvent = procedure(ASender: TObject; var VAcct: string) of object;
TIdFTPBannerEvent = procedure (ASender: TObject; const AMsg : String) of object;
TIdFTPClientIdentifier = class (TPersistent)
protected
FClientName : String;
FClientVersion : String;
FPlatformDescription : String;
procedure SetClientName(const AValue: String);
procedure SetClientVersion(const AValue: String);
procedure SetPlatformDescription(const AValue: String);
function GetClntOutput: String;
public
procedure Assign(Source: TPersistent); override;
property ClntOutput : String read GetClntOutput;
published
property ClientName : String read FClientName write SetClientName;
property ClientVersion : String read FClientVersion write SetClientVersion;
property PlatformDescription : String read FPlatformDescription write SetPlatformDescription;
end;
TIdFtpProxySettings = class (TPersistent)
protected
FHost, FUserName, FPassword: String;
FProxyType: TIdFtpProxyType;
FPort: TIdPort;
public
procedure Assign(Source: TPersistent); override;
published
property ProxyType: TIdFtpProxyType read FProxyType write FProxyType;
property Host: String read FHost write FHost;
property UserName: String read FUserName write FUserName;
property Password: String read FPassword write FPassword;
property Port: TIdPort read FPort write FPort;
end;
TIdFTPTZInfo = class(TPersistent)
protected
FGMTOffset : TDateTime;
FGMTOffsetAvailable : Boolean;
public
procedure Assign(Source: TPersistent); override;
published
property GMTOffset : TDateTime read FGMTOffset write FGMTOffset;
property GMTOffsetAvailable : Boolean read FGMTOffsetAvailable write FGMTOffsetAvailable;
end;
TIdFTPKeepAlive = class(TPersistent)
protected
FUseKeepAlive: Boolean;
FIdleTimeMS: Integer;
FIntervalMS: Integer;
public
procedure Assign(Source: TPersistent); override;
published
property UseKeepAlive: Boolean read FUseKeepAlive write FUseKeepAlive;
property IdleTimeMS: Integer read FIdleTimeMS write FIdleTimeMS;
property IntervalMS: Integer read FIntervalMS write FIntervalMS;
end;
TIdFTP = class(TIdExplicitTLSClient)
protected
FAutoLogin: Boolean;
FAutoIssueFEAT : Boolean;
FCurrentTransferMode : TIdFTPTransferMode;
FClientInfo : TIdFTPClientIdentifier;
FDataSettingsSent: Boolean; // only send SSL data settings once per connection
FUsingSFTP : Boolean; //enable SFTP internel flag
FUsingCCC : Boolean; //are we using FTP with SSL on a clear control channel?
FUseHOST: Boolean;
FServerHOST: String;
FCanUseMLS : Boolean; //can we use MLISx instead of LIST
FUsingExtDataPort : Boolean; //are NAT Extensions (RFC 2428 available) flag
FUsingNATFastTrack : Boolean;//are we using NAT fastrack feature
FCanResume: Boolean;
FListResult: TStrings;
FLoginMsg: TIdReplyFTP;
FPassive: Boolean;
FPassiveUseControlHost: Boolean;
FDataPortProtection : TIdFTPDataPortSecurity;
FAUTHCmd : TAuthCmd;
FDataPort: TIdPort;
FDataPortMin: TIdPort;
FDataPortMax: TIdPort;
FDefStringEncoding: IIdTextEncoding;
FExternalIP : String;
FResumeTested: Boolean;
FServerDesc: string;
FSystemDesc: string;
FTransferType: TIdFTPTransferType;
FTransferTimeout : Integer;
FListenTimeout : Integer;
FDataChannel: TIdTCPConnection;
FDirectoryListing: TIdFTPListItems;
FDirFormat : String;
FListParserClass : TIdFTPListParseClass;
FOnAfterClientLogin: TNotifyEvent;
FOnCreateFTPList: TIdCreateFTPList;
FOnBeforeGet: TNotifyEvent;
FOnBeforePut: TIdFtpAfterGet;
//in case someone needs to do something special with the data being uploaded
FOnAfterGet: TIdFtpAfterGet; //APR
FOnAfterPut: TNotifyEvent; //JPM at Don Sider's suggestion
FOnNeedAccount: TIdNeedAccountEvent;
FOnCustomFTPProxy : TNotifyEvent;
FOnDataChannelCreate: TIdOnDataChannelCreate;
FOnDataChannelDestroy: TIdOnDataChannelDestroy;
FProxySettings: TIdFtpProxySettings;
FUseExtensionDataPort : Boolean;
FTryNATFastTrack : Boolean;
FUseMLIS : Boolean;
FLangsSupported : TStrings;
FUseCCC: Boolean;
//is the SSCN Client method on for this connection?
FSSCNOn : Boolean;
FIsCompressionSupported : Boolean;
FOnBannerBeforeLogin : TIdFTPBannerEvent;
FOnBannerAfterLogin : TIdFTPBannerEvent;
FOnBannerWarning : TIdFTPBannerEvent;
FTZInfo : TIdFTPTZInfo;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FCompressor : TIdZLibCompressorBase;
//ZLib settings
FZLibCompressionLevel : Integer; //7
FZLibWindowBits : Integer; //-15
FZLibMemLevel : Integer; //8
FZLibStratagy : Integer; //0 - default
//dir events for some GUI programs.
//The directory was Retrieved from the FTP server.
FOnRetrievedDir : TNotifyEvent;
//parsing is done only when DirectoryListing is referenced
FOnDirParseStart : TNotifyEvent;
FOnDirParseEnd : TNotifyEvent;
//we probably need an Abort flag so we know when an abort is sent.
//It turns out that one server will send a 550 or 451 error followed by an
//ABOR successfull
FAbortFlag : TIdThreadSafeBoolean;
FAccount: string;
FNATKeepAlive: TIdFTPKeepAlive;
//
procedure DoOnDataChannelCreate;
procedure DoOnDataChannelDestroy;
procedure DoOnRetrievedDir;
procedure DoOnDirParseStart;
procedure DoOnDirParseEnd;
procedure FinalizeDataOperation;
procedure SetTZInfo(const Value: TIdFTPTZInfo);
function IsSiteZONESupported : Boolean;
function IndexOfFeatLine(const AFeatLine : String):Integer;
procedure ClearSSCN;
function SetSSCNToOn : Boolean;
procedure SendInternalPassive(const ACmd : String; var VIP: string; var VPort: TIdPort);
procedure SendCPassive(var VIP: string; var VPort: TIdPort);
function FindAuthCmd : String;
//
function GetReplyClass: TIdReplyClass; override;
//
procedure ParseFTPList;
procedure SetPassive(const AValue : Boolean);
procedure SetTryNATFastTrack(const AValue: Boolean);
procedure DoTryNATFastTrack;
procedure SetUseExtensionDataPort(const AValue: Boolean);
procedure SetIPVersion(const AValue: TIdIPVersion); override;
procedure SetIOHandler(AValue: TIdIOHandler); override;
function GetSupportsTLS: Boolean; override;
procedure ConstructDirListing;
procedure DoAfterLogin;
procedure DoFTPList;
procedure DoCustomFTPProxy;
procedure DoOnBannerAfterLogin(AText : TStrings);
procedure DoOnBannerBeforeLogin(AText : TStrings);
procedure DoOnBannerWarning(AText : TStrings);
procedure SendPBSZ; //protection buffer size
procedure SendPROT; //data port protection
procedure SendDataSettings; //this is for the extensions only;
// procedure DoCheckListFormat(const ALine: String);
function GetDirectoryListing: TIdFTPListItems;
// function GetOnParseCustomListFormat: TIdOnParseCustomListFormat;
procedure InitDataChannel;
//PRET is to help distributed FTP systems by letting them know what you will do
//before issuing a PASV. See: http://drftpd.mog.se/wiki/wiki.phtml?title=Distributed_PASV#PRE_Transfer_Command_for_Distributed_PASV_Transfers
//for a discussion.
procedure SendPret(const ACommand : String);
procedure InternalGet(const ACommand: string; ADest: TStream; AResume: Boolean = false);
procedure InternalPut(const ACommand: string; ASource: TStream; AFromBeginning: Boolean = True; AResume: Boolean = False);
// procedure SetOnParseCustomListFormat(const AValue: TIdOnParseCustomListFormat);
procedure SendPassive(var VIP: string; var VPort: TIdPort);
procedure SendPort(AHandle: TIdSocketHandle); overload;
procedure SendPort(const AIP : String; const APort : TIdPort); overload;
procedure ParseEPSV(const AReply : String; var VIP : String; var VPort : TIdPort);
//These two are for RFC 2428.txt
procedure SendEPort(AHandle: TIdSocketHandle); overload;
procedure SendEPort(const AIP : String; const APort : TIdPort; const AIPVersion : TIdIPVersion); overload;
procedure SendEPassive(var VIP: string; var VPort: TIdPort);
function SendHost: Int16;
procedure SetProxySettings(const Value: TIdFtpProxySettings);
procedure SetClientInfo(const AValue: TIdFTPClientIdentifier);
procedure SetCompressor(AValue: TIdZLibCompressorBase);
procedure SendTransferType(AValue: TIdFTPTransferType);
procedure SetTransferType(AValue: TIdFTPTransferType);
procedure DoBeforeGet; virtual;
procedure DoBeforePut(AStream: TStream); virtual;
procedure DoAfterGet(AStream: TStream); virtual; //APR
procedure DoAfterPut; virtual;
class procedure FXPSetTransferPorts(AFromSite, AToSite: TIdFTP; const ATargetUsesPasv : Boolean);
class procedure FXPSendFile(AFromSite, AToSite: TIdFTP; const ASourceFile, ADestFile: String);
class function InternalEncryptedTLSFXP(AFromSite, AToSite: TIdFTP; const ASourceFile, ADestFile: String; const ATargetUsesPasv : Boolean) : Boolean;
class function InternalUnencryptedFXP(AFromSite, AToSite: TIdFTP; const ASourceFile, ADestFile: String; const ATargetUsesPasv : Boolean): Boolean;
class function ValidateInternalIsTLSFXP(AFromSite, AToSite: TIdFTP; const ATargetUsesPasv : Boolean): Boolean;
procedure InitComponent; override;
procedure SetUseTLS(AValue : TIdUseTLS); override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure SetDataPortProtection(AValue : TIdFTPDataPortSecurity);
procedure SetAUTHCmd(const AValue : TAuthCmd);
procedure SetDefStringEncoding(AValue: IIdTextEncoding);
procedure SetUseCCC(const AValue: Boolean);
procedure SetNATKeepAlive(AValue: TIdFTPKeepAlive);
procedure IssueFEAT;
//specific server detection
function IsOldServU: Boolean;
function IsBPFTP : Boolean;
function IsTitan : Boolean;
function IsWSFTP : Boolean;
function IsIIS: Boolean;
function CheckAccount: Boolean;
function IsAccountNeeded : Boolean;
function GetSupportsVerification : Boolean;
public
procedure GetInternalResponse(AEncoding: IIdTextEncoding = nil); override;
function CheckResponse(const AResponse: Int16; const AAllowedResponses: array of Int16): Int16; override;
function IsExtSupported(const ACmd : String):Boolean;
procedure ExtractFeatFacts(const ACmd : String; AResults : TStrings);
//this function transparantly handles OTP based on the Last command response
//so it needs to be called only after the USER command or equivilent.
function GetLoginPassword : String; overload;
function GetLoginPassword(const APrompt : String) : String; overload;
procedure Abort; virtual;
procedure Allocate(AAllocateBytes: Integer);
procedure ChangeDir(const ADirName: string);
procedure ChangeDirUp;
procedure Connect; override;
destructor Destroy; override;
procedure Delete(const AFilename: string);
procedure FileStructure(AStructure: TIdFTPDataStructure);
procedure Get(const ASourceFile: string; ADest: TStream; AResume: Boolean = false); overload;
procedure Get(const ASourceFile, ADestFile: string; const ACanOverwrite: boolean = false; AResume: Boolean = false); overload;
procedure Help(AHelpContents: TStrings; ACommand: String = '');
procedure KillDataChannel; virtual;
//.NET Overload
procedure List; overload;
//.NET Overload
procedure List(const ASpecifier: string; ADetails: Boolean = True); overload;
procedure List(ADest: TStrings; const ASpecifier: string = ''; ADetails: Boolean = True); overload;
procedure ExtListDir(ADest: TStrings = nil; const ADirectory: string = '');
procedure ExtListItem(ADest: TStrings; AFList : TIdFTPListItems; const AItem: string=''); overload;
procedure ExtListItem(ADest: TStrings; const AItem: string = ''); overload;
procedure ExtListItem(AFList : TIdFTPListItems; const AItem : String= ''); overload;
function FileDate(const AFileName : String; const AsGMT : Boolean = False): TDateTime;
procedure Login;
procedure MakeDir(const ADirName: string);
procedure Noop;
procedure SetCmdOpt(const ACMD, AOptions : String);
procedure Put(const ASource: TStream; const ADestFile: string;
const AAppend: Boolean = False; const AStartPos: TIdStreamSize = -1); overload;
procedure Put(const ASourceFile: string; const ADestFile: string = '';
const AAppend: Boolean = False; const AStartPos: TIdStreamSize = -1); overload;
procedure StoreUnique(const ASource: TStream; const AStartPos: TIdStreamSize = -1); overload;
procedure StoreUnique(const ASourceFile: string; const AStartPos: TIdStreamSize = -1); overload;
procedure SiteToSiteUpload(const AToSite : TIdFTP; const ASourceFile : String; const ADestFile : String = '');
procedure SiteToSiteDownload(const AFromSite: TIdFTP; const ASourceFile : String; const ADestFile : String = '');
procedure DisconnectNotifyPeer; override;
procedure Quit; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPECATED_MSG} 'Use Disconnect() instead'{$ENDIF};{$ENDIF}
function Quote(const ACommand: String): Int16;
procedure RemoveDir(const ADirName: string);
procedure Rename(const ASourceFile, ADestFile: string);
function ResumeSupported: Boolean;
function RetrieveCurrentDir: string;
procedure Site(const ACommand: string);
function Size(const AFileName: String): Int64;
procedure Status(AStatusList: TStrings);
procedure StructureMount(APath: String);
procedure TransferMode(ATransferMode: TIdFTPTransferMode);
procedure ReInitialize(ADelay: UInt32 = 10);
procedure SetLang(const ALangTag : String);
function CRC(const AFIleName : String; const AStartPoint : Int64 = 0; const AEndPoint : Int64=0) : Int64;
//verify file was uploaded, this is more comprehensive than the above
function VerifyFile(ALocalFile : TStream; const ARemoteFile : String;
const AStartPoint : TIdStreamSize = 0; const AByteCount : TIdStreamSize = 0) : Boolean; overload;
function VerifyFile(const ALocalFile, ARemoteFile : String;
const AStartPoint : TIdStreamSize = 0; const AByteCount : TIdStreamSize = 0) : Boolean; overload;
//file parts must be in order in TStrings parameter
//GlobalScape FTP Pro uses this for multipart simultanious file uploading
procedure CombineFiles(const ATargetFile : String; AFileParts : TStrings);
//Set modified file time.
procedure SetModTime(const AFileName: String; const ALocalTime: TDateTime);
procedure SetModTimeGMT(const AFileName : String; const AGMTTime: TDateTime);
// servers that support MDTM yyyymmddhhmmss[+-xxx] and also support LIST -T
//This is true for servers that are known to support these even if they aren't
//listed in the FEAT reply.
function IsServerMDTZAndListTForm : Boolean;
property IsCompressionSupported : Boolean read FIsCompressionSupported;
//
property SupportsVerification : Boolean read GetSupportsVerification;
property CanResume: Boolean read ResumeSupported;
property CanUseMLS : Boolean read FCanUseMLS;
property DirectoryListing: TIdFTPListItems read GetDirectoryListing;
property DirFormat : String read FDirFormat;
property LangsSupported : TStrings read FLangsSupported;
property ListParserClass : TIdFTPListParseClass read FListParserClass write FListParserClass;
property LoginMsg: TIdReplyFTP read FLoginMsg;
property ListResult: TStrings read FListResult;
property SystemDesc: string read FSystemDesc;
property TZInfo : TIdFTPTZInfo read FTZInfo write SetTZInfo;
property UsingExtDataPort : Boolean read FUsingExtDataPort;
property UsingNATFastTrack : Boolean read FUsingNATFastTrack;
property UsingSFTP : Boolean read FUsingSFTP;
property CurrentTransferMode : TIdFTPTransferMode read FCurrentTransferMode write TransferMode;
published
{$IFDEF DOTNET}
{$IFDEF DOTNET_2_OR_ABOVE}
property IPVersion;
{$ENDIF}
{$ELSE}
property IPVersion;
{$ENDIF}
property AutoIssueFEAT : Boolean read FAutoIssueFEAT write FAutoIssueFEAT default DEF_Id_FTP_AutoIssueFEAT;
property AutoLogin: Boolean read FAutoLogin write FAutoLogin default DEF_Id_FTP_AutoLogin;
// This is an object that can compress and decompress FTP Deflate encoding
property Compressor : TIdZLibCompressorBase read FCompressor write SetCompressor;
property Host;
property UseCCC : Boolean read FUseCCC write SetUseCCC default DEF_Id_FTP_UseCCC;
property Passive: boolean read FPassive write SetPassive default Id_TIdFTP_Passive;
property PassiveUseControlHost: Boolean read FPassiveUseControlHost write FPassiveUseControlHost default DEF_Id_FTP_PassiveUseControlHost;
property DataPortProtection : TIdFTPDataPortSecurity read FDataPortProtection write SetDataPortProtection default Id_TIdFTP_DataPortProtection;
property AUTHCmd : TAuthCmd read FAUTHCmd write SetAUTHCmd default DEF_Id_FTP_AUTH_CMD;
property ConnectTimeout;
property DataPort: TIdPort read FDataPort write FDataPort default 0;
property DataPortMin: TIdPort read FDataPortMin write FDataPortMin default 0;
property DataPortMax: TIdPort read FDataPortMax write FDataPortMax default 0;
property DefStringEncoding : IIdTextEncoding read FDefStringEncoding write SetDefStringEncoding;
property ExternalIP : String read FExternalIP write FExternalIP;
property Password;
property TransferType: TIdFTPTransferType read FTransferType write SetTransferType default Id_TIdFTP_TransferType;
property TransferTimeout: Integer read FTransferTimeout write FTransferTimeout default IdDefTimeout;
property ListenTimeout : Integer read FListenTimeout write FListenTimeout default DEF_Id_FTP_ListenTimeout;
property Username;
property Port default IDPORT_FTP;
property UseExtensionDataPort : Boolean read FUseExtensionDataPort write SetUseExtensionDataPort default DEF_Id_TIdFTP_UseExtendedData;
property UseMLIS : Boolean read FUseMLIS write FUseMLIS default DEF_Id_TIdFTP_UseMIS;
property TryNATFastTrack : Boolean read FTryNATFastTrack write SetTryNATFastTrack default Id_TIdFTP_UseNATFastTrack;
property NATKeepAlive: TIdFTPKeepAlive read FNATKeepAlive write SetNATKeepAlive;
property ProxySettings: TIdFtpProxySettings read FProxySettings write SetProxySettings;
property Account: string read FAccount write FAccount;
property ClientInfo : TIdFTPClientIdentifier read FClientInfo write SetClientInfo;
property UseHOST: Boolean read FUseHOST write FUseHOST default DEF_Id_FTP_UseHOST;
property ServerHOST: String read FServerHOST write FServerHOST;
property UseTLS;
property OnTLSNotAvailable;
property OnBannerBeforeLogin : TIdFTPBannerEvent read FOnBannerBeforeLogin write FOnBannerBeforeLogin;
property OnBannerAfterLogin : TIdFTPBannerEvent read FOnBannerAfterLogin write FOnBannerAfterLogin;
property OnBannerWarning : TIdFTPBannerEvent read FOnBannerWarning write FOnBannerWarning;
property OnAfterClientLogin: TOnAfterClientLogin read FOnAfterClientLogin write FOnAfterClientLogin;
property OnCreateFTPList: TIdCreateFTPList read FOnCreateFTPList write FOnCreateFTPList;
property OnAfterGet: TIdFtpAfterGet read FOnAfterGet write FOnAfterGet; //APR
property OnAfterPut: TNotifyEvent read FOnAfterPut write FOnAfterPut;
property OnNeedAccount: TIdNeedAccountEvent read FOnNeedAccount write FOnNeedAccount;
property OnCustomFTPProxy : TNotifyEvent read FOnCustomFTPProxy write FOnCustomFTPProxy;
property OnDataChannelCreate: TIdOnDataChannelCreate read FOnDataChannelCreate write FOnDataChannelCreate;
property OnDataChannelDestroy: TIdOnDataChannelDestroy read FOnDataChannelDestroy write FOnDataChannelDestroy;
//The directory was Retrieved from the FTP server.
property OnRetrievedDir : TNotifyEvent read FOnRetrievedDir write FOnRetrievedDir;
//parsing is done only when DirectoryLiusting is referenced
property OnDirParseStart : TNotifyEvent read FOnDirParseStart write FOnDirParseStart;
property OnDirParseEnd : TNotifyEvent read FOnDirParseEnd write FOnDirParseEnd;
property ReadTimeout default DEF_Id_FTP_READTIMEOUT;
end;
EIdFTPException = class(EIdException);
EIdFTPFileAlreadyExists = class(EIdFTPException);
EIdFTPMustUseExtWithIPv6 = class(EIdFTPException);
EIdFTPMustUseExtWithNATFastTrack = class(EIdFTPException);
EIdFTPPassiveMustBeTrueWithNATFT = class(EIdFTPException);
EIdFTPServerSentInvalidPort = class(EIdFTPException);
EIdFTPSiteToSiteTransfer = class(EIdFTPException);
EIdFTPSToSNATFastTrack = class(EIdFTPSiteToSiteTransfer);
EIdFTPSToSNoDataProtection = class(EIdFTPSiteToSiteTransfer);
EIdFTPSToSIPProtoMustBeSame = class(EIdFTPSiteToSiteTransfer);
EIdFTPSToSBothMostSupportSSCN = class(EIdFTPSiteToSiteTransfer);
EIdFTPSToSTransModesMustBeSame = class(EIdFTPSiteToSiteTransfer);
EIdFTPOnCustomFTPProxyRequired = class(EIdFTPException);
EIdFTPConnAssuranceFailure = class(EIdFTPException);
EIdFTPWrongIOHandler = class(EIdFTPException);
EIdFTPUploadFileNameCanNotBeEmpty = class(EIdFTPException);
EIdFTPDataPortProtection = class(EIdFTPException);
EIdFTPNoDataPortProtectionAfterCCC = class(EIdFTPDataPortProtection);
EIdFTPNoDataPortProtectionWOEncryption = class(EIdFTPDataPortProtection);
EIdFTPNoCCCWOEncryption = class(EIdFTPException);
EIdFTPAUTHException = class(EIdFTPException);
EIdFTPNoAUTHWOSSL = class(EIdFTPAUTHException);
EIdFTPCanNotSetAUTHCon = class(EIdFTPAUTHException);
EIdFTPMissingCompressor = class(EIdFTPException);
EIdFTPCompressorNotReady = class(EIdFTPException);
EIdFTPUnsupportedTransferMode = class(EIdFTPException);
EIdFTPUnsupportedTransferType = class(EIdFTPException);
implementation
uses
//facilitate inlining only.
{$IFDEF KYLIXCOMPAT}
Libc,
{$ENDIF}
{$IFDEF USE_VCL_POSIX}
Posix.SysSelect,
Posix.SysTime,
Posix.Unistd,
{$ENDIF}
{$IFDEF WINDOWS}
//facilitate inlining only.
Windows,
{$ENDIF}
{$IFDEF DOTNET}
{$IFDEF USE_INLINE}
System.IO,
System.Threading,
{$ENDIF}
{$ENDIF}
IdComponent,
IdFIPS,
IdResourceStringsCore, IdIOHandlerStack, IdResourceStringsProtocols,
IdSSL, IdGlobalProtocols, IdHash, IdHashCRC, IdHashSHA, IdHashMessageDigest,
IdStack, IdStackConsts, IdSimpleServer, IdOTPCalculator, SysUtils;
const
cIPVersions: array[TIdIPVersion] of String = ('1', '2'); {do not localize}
type
TIdFTPListResult = class(TStringList)
private
FDetails: Boolean; //Did the developer use the NLST command for the last list command
FUsedMLS : Boolean; //Did the developer use MLSx commands for the last list command
public
property Details: Boolean read FDetails;
property UsedMLS: Boolean read FUsedMLS;
end;
procedure TIdFTP.InitComponent;
begin
inherited InitComponent;
//
FAutoLogin := DEF_Id_FTP_AutoLogin;
FRegularProtPort := IdPORT_FTP;
FImplicitTLSProtPort := IdPORT_ftps;
//
Port := IDPORT_FTP;
Passive := Id_TIdFTP_Passive;
FPassiveUseControlHost := DEF_Id_FTP_PassiveUseControlHost;
FDataPortProtection := Id_TIdFTP_DataPortProtection;
FUseCCC := DEF_Id_FTP_UseCCC;
FAUTHCmd := DEF_Id_FTP_AUTH_CMD;
FUseHOST := DEF_Id_FTP_UseHOST;
FDataPort := 0;
FDataPortMin := 0;
FDataPortMax := 0;
FDefStringEncoding := IndyTextEncoding_8Bit;
FUseExtensionDataPort := DEF_Id_TIdFTP_UseExtendedData;
FTryNATFastTrack := Id_TIdFTP_UseNATFastTrack;
FTransferType := Id_TIdFTP_TransferType;
FTransferTimeout := IdDefTimeout;
FListenTimeout := DEF_Id_FTP_ListenTimeout;
FLoginMsg := TIdReplyFTP.Create(nil);
FListResult := TIdFTPListResult.Create;
FLangsSupported := TStringList.Create;
FCanResume := False;
FResumeTested := False;
FProxySettings:= TIdFtpProxySettings.Create; //APR
FClientInfo := TIdFTPClientIdentifier.Create;
FTZInfo := TIdFTPTZInfo.Create;
FTZInfo.FGMTOffsetAvailable := False;
FUseMLIS := DEF_Id_TIdFTP_UseMIS;
FCanUseMLS := False; //initialize MLIS flags
//Settings specified by
// http://www.ietf.org/internet-drafts/draft-preston-ftpext-deflate-00.txt
FZLibCompressionLevel := DEF_ZLIB_COMP_LEVEL;
FZLibWindowBits := DEF_ZLIB_WINDOW_BITS; //-15 - no extra headers
FZLibMemLevel := DEF_ZLIB_MEM_LEVEL;
FZLibStratagy := DEF_ZLIB_STRATAGY; // - default
//
FAbortFlag := TIdThreadSafeBoolean.Create;
FAbortFlag.Value := False;
{
Some firewalls don't handle control connections properly during long
data transfers. They will timeout the control connection because it
is idle and making it worse is that they will chop off a connection
instead of closing it, causing TIdFTP to wait forever for nothing.
}
FNATKeepAlive := TIdFTPKeepAlive.Create;
ReadTimeout := DEF_Id_FTP_READTIMEOUT;
FAutoIssueFEAT := DEF_Id_FTP_AutoIssueFEAT;
end;
{$IFNDEF HAS_TryEncodeTime}
// TODO: move this to IdGlobal or IdGlobalProtocols...
function TryEncodeTime(Hour, Min, Sec, MSec: Word; out VTime: TDateTime): Boolean;
begin
try
VTime := EncodeTime(Hour, Min, Sec, MSec);
Result := True;
except
Result := False;
end;
end;
{$ENDIF}
{$IFNDEF HAS_TryStrToInt}
// TODO: use the implementation already in IdGlobalProtocols...
function TryStrToInt(const S: string; out Value: Integer): Boolean;
{$IFDEF USE_INLINE}inline;{$ENDIF}
var
E: Integer;
begin
Val(S, Value, E);
Result := E = 0;
end;
{$ENDIF}
procedure TIdFTP.Connect;
var
LHost: String;
LPort: TIdPort;
LBuf : String;
LSendQuitOnError: Boolean;
LOffs: Integer;
LRetryWithoutHOST: Boolean;
begin
LSendQuitOnError := False;
FCurrentTransferMode := dmStream;
FTZInfo.FGMTOffsetAvailable := False;
//FSSCNOn should be set to false to prevent problems.
FSSCNOn := False;
FUsingSFTP := False;
FUsingCCC := False;
FDataSettingsSent := False;
if FUseExtensionDataPort then begin
FUsingExtDataPort := True;
end;
FUsingNATFastTrack := False;
FCapabilities.Clear;
try
//APR 011216: proxy support
LHost := FHost;
LPort := FPort;
try
//I think fpcmTransparent means to connect to the regular host and the firewalll
//intercepts the login information.
if (ProxySettings.ProxyType <> fpcmNone) and (ProxySettings.ProxyType <> fpcmTransparent) and
(Length(ProxySettings.Host) > 0) then begin
FHost := ProxySettings.Host;
FPort := ProxySettings.Port;
end;
if FUseTLS = utUseImplicitTLS then begin
//at this point, we treat implicit FTP as if it were explicit FTP with TLS
FUsingSFTP := True;
end;
inherited Connect;
finally
FHost := LHost;
FPort := LPort;
end;
// RLebeau: must not send/receive UTF-8 before negotiating for it...
IOHandler.DefStringEncoding := FDefStringEncoding;
{$IFDEF STRING_IS_ANSI}
IOHandler.DefAnsiEncoding := IndyTextEncoding_OSDefault;
{$ENDIF}
// RLebeau: RFC 959 says that the greeting can be preceeded by a 1xx
// reply and that the client should wait for the 220 reply when this
// happens. Also, the RFC says that 120 should be used, but some
// servers use other 1xx codes, such as 130, so handle 1xx generically
// calling GetInternalResponse() directly to avoid duplicate calls
// to CheckResponse() for the initial response if it is not 1xx
GetInternalResponse;
if (LastCmdResult.NumericCode div 100) = 1 then begin
DoOnBannerWarning(LastCmdResult.FormattedReply);
GetResponse(220);
end else begin
CheckResponse(LastCmdResult.NumericCode, [220]);
end;
LSendQuitOnError := True;
FGreeting.Assign(LastCmdResult);
// Save initial greeting for server identification in case FGreeting changes
// in response to the HOST command
if FGreeting.Text.Count > 0 then begin
FServerDesc := FGreeting.Text[0];
end else begin
FServerDesc := '';
end;
// Implement HOST command as specified by
// http://tools.ietf.org/html/draft-hethmon-mcmurray-ftp-hosts-01
// Do not check the response for failures. The draft suggests allowing
// 220 (success) and 500/502 (unsupported), but vsftpd returns 530, and
// whatever ftp.microsoft.com is running returns 504.
if UseHOST then begin
// RLebeau: WS_FTP Server 5.x disconnects if the command fails,
// whereas WS_FTP Server 6+ does not. If the server disconnected
// here, let's mimic FTP Voyager by reconnecting without using
// the HOST command again...
//
// RLebeau 11/18/2013: some other servers also disconnect on a failed
// HOST command, so no longer retrying connect for WSFTP exclusively...
//
// RLebeau 11/22/2014: encountered one case where the server disconnects
// before the reply is received. So checking for that as well...
//
LRetryWithoutHOST := False;
try
if SendHost() <> 220 then begin
IOHandler.CheckForDisconnect(True, True);
end;
except
on E: EIdConnClosedGracefully do begin
LRetryWithoutHOST := True;
end;
on E: EIdSocketError do begin
if (E.LastError = Id_WSAECONNABORTED) or (E.LastError = Id_WSAECONNRESET) then begin
LRetryWithoutHOST := True;
end else begin
raise;
end;
end;
end;
if LRetryWithoutHOST then
begin
Disconnect(False);
if Assigned(IOHandler) then begin
IOHandler.InputBuffer.Clear;
end;
UseHOST := False;
try
Connect;
finally
UseHOST := True;
end;
Exit;
end;
end else begin
FGreeting.Assign(LastCmdResult);
end;
DoOnBannerBeforeLogin (FGreeting.FormattedReply);
// RLebeau: having an AutoIssueFeat property doesn't make sense to
// me. There are commands below that require FEAT's response, but
// if the user sets AutoIssueFeat to False, these commands will not
// be allowed to execute!
if AutoLogin then begin
Login;
DoAfterLogin;
//Fast track is set only one time per connection and no more, even
//with REINIT
if TryNATFastTrack then begin
DoTryNATFastTrack;
end;
if FUseTLS = utUseImplicitTLS then begin
//at this point, we treat implicit FTP as if it were explicit FTP with TLS
FUsingSFTP := True;
end;
// OpenVMS 7.1 replies with 200 instead of 215 - What does the RFC say about this?
// if SendCmd('SYST', [200, 215, 500]) = 500 then begin {do not localize}
//Do not fault if SYST was not understood by the server. Novel Netware FTP
//may not understand SYST.
if SendCmd('SYST') = 500 then begin {do not localize}
FSystemDesc := RSFTPUnknownHost;
end else begin
FSystemDesc := LastCmdResult.Text[0];
end;
if IsSiteZONESupported then begin
if SendCmd('SITE ZONE') = 210 then begin {do not localize}
if LastCmdResult.Text.Count > 0 then begin
LBuf := LastCmdResult.Text[0];
// some servers (Serv-U, etc) use a 'UTC' offset string, ie
// "UTC-300", specifying the number of minutes from UTC. Other
// servers (Apache) use a GMT offset string instead, ie "-0300".
if TextStartsWith(LBuf, 'UTC-') then begin {do not localize}
// Titan FTP 6.26.634 incorrectly returns UTC-2147483647 when it's
// first installed.
FTZInfo.FGMTOffsetAvailable :=
TryStrToInt(Copy(LBuf, 4, MaxInt), LOffs) and
TryEncodeTime(Abs(LOffs) div 60, Abs(LOffs) mod 60, 0, 0, FTZInfo.FGMTOffset);
if FTZInfo.FGMTOffsetAvailable and (LOffs < 0) then
FTZInfo.FGMTOffset := -FTZInfo.FGMTOffset
end else begin
FTZInfo.FGMTOffsetAvailable := True;
FTZInfo.GMTOffset := GmtOffsetStrToDateTime(LBuf);
end;
end;
end;
end;
SendTransferType(FTransferType);
DoStatus(ftpReady, [RSFTPStatusReady]);
end else begin
// OpenVMS 7.1 replies with 200 instead of 215 - What does the RFC say about this?
// if SendCmd('SYST', [200, 215, 500]) = 500 then begin {do not localize}
//Do not fault if SYST was not understood by the server. Novel Netware FTP
//may not understand SYST.
if SendCmd('SYST') = 500 then begin {do not localize}
FSystemDesc := RSFTPUnknownHost;
end else begin
FSystemDesc := LastCmdResult.Text[0];
end;
if FAutoIssueFEAT then begin
IssueFEAT;
end;
end;
except
Disconnect(LSendQuitOnError); // RLebeau: do not send the QUIT command if the greeting was not received
raise;
end;
end;
function TIdFTP.SendHost: Int16;
var
LHost: String;
begin
LHost := FServerHOST;
if LHost = '' then begin
LHost := FHost;
end;
if Socket <> nil then begin
if LHost = Socket.Binding.PeerIP then begin
LHost := '[' + LHost + ']'; {do not localize}
end;
end;
Result := SendCmd('HOST ' + LHost); {do not localize}
end;
procedure TIdFTP.SetTransferType(AValue: TIdFTPTransferType);
begin
if AValue <> FTransferType then begin
if not Assigned(FDataChannel) then begin
if Connected then begin
SendTransferType(AValue);
end;
FTransferType := AValue;
end;
end;
end;
procedure TIdFTP.SendTransferType(AValue: TIdFTPTransferType);
var
s: string;
begin
s := '';
case AValue of
ftAscii: s := 'A'; {do not localize}
ftBinary: s := 'I'; {do not localize}
else
raise EIdFTPUnsupportedTransferType.Create(RSFTPUnsupportedTransferType);
end;
SendCmd('TYPE ' + s, 200); {do not localize}
end;
function TIdFTP.ResumeSupported: Boolean;
begin
if not FResumeTested then begin
FResumeTested := True;
FCanResume := Quote('REST 1') = 350; {do not localize}
Quote('REST 0'); {do not localize}
end;
Result := FCanResume;
end;
procedure TIdFTP.Get(const ASourceFile: string; ADest: TStream; AResume: Boolean = False);
begin
//for SSL FXP, we have to do it here because InternalGet is used by the LIST command
//where SSCN is ignored.
ClearSSCN;
AResume := AResume and CanResume;
DoBeforeGet;
// RLebeau 7/26/06: do not do this! It breaks the ability to resume files
// ADest.Position := 0;
InternalGet('RETR ' + ASourceFile, ADest, AResume);
DoAfterGet(ADest);
end;
procedure TIdFTP.Get(const ASourceFile, ADestFile: string; const ACanOverwrite: Boolean = False;
AResume: Boolean = False);
var
LDestStream: TStream;
begin
AResume := AResume and CanResume;
if ACanOverwrite and (not AResume) then begin
SysUtils.DeleteFile(ADestFile);
LDestStream := TIdFileCreateStream.Create(ADestFile);
end
else if (not ACanOverwrite) and AResume then begin
LDestStream := TIdAppendFileStream.Create(ADestFile);
end
else if not FileExists(ADestFile) then begin
LDestStream := TIdFileCreateStream.Create(ADestFile);
end
else begin
raise EIdFTPFileAlreadyExists.Create(RSDestinationFileAlreadyExists);
end;
try
Get(ASourceFile, LDestStream, AResume);
finally
FreeAndNil(LDestStream);
end;
end;
procedure TIdFTP.DoBeforeGet;
begin
if Assigned(FOnBeforeGet) then begin
FOnBeforeGet(Self);
end;
end;
procedure TIdFTP.DoBeforePut(AStream: TStream);
begin
if Assigned(FOnBeforePut) then begin
FOnBeforePut(Self, AStream);
end;
end;
procedure TIdFTP.DoAfterGet(AStream: TStream);//APR
begin
if Assigned(FOnAfterGet) then begin
FOnAfterGet(Self, AStream);
end;
end;
procedure TIdFTP.DoAfterPut;
begin
if Assigned(FOnAfterPut) then begin
FOnAfterPut(Self);
end;
end;
procedure TIdFTP.ConstructDirListing;
begin
if not Assigned(FDirectoryListing) then begin
if not IsDesignTime then begin
DoFTPList;
end;
if not Assigned(FDirectoryListing) then begin
FDirectoryListing := TIdFTPListItems.Create;
end;
end else begin
FDirectoryListing.Clear;
end;
end;
procedure TIdFTP.List(ADest: TStrings; const ASpecifier: string = ''; ADetails: Boolean = True); {do not localize}
var
LDest: TMemoryStream;
LTrans : TIdFTPTransferType;
begin
if ADetails and UseMLIS and FCanUseMLS then begin
ExtListDir(ADest, ASpecifier);
Exit;
end;
// Note that for LIST, it might be best to put the connection in ASCII mode
// because some old servers such as TOPS20 might require this. We restore
// it if the original mode was not ASCII. It's a good idea to do this
// anyway because some clients still do this such as WS_FTP Pro and
// Microsoft's FTP Client.
LTrans := TransferType;
if LTrans <> ftASCII then begin
Self.TransferType := ftASCII;
end;
try
LDest := TMemoryStream.Create;
try
InternalGet(Trim(iif(ADetails, 'LIST', 'NLST') + ' ' + ASpecifier), LDest); {do not localize}
FreeAndNil(FDirectoryListing);
FDirFormat := '';
LDest.Position := 0;
FListResult.Text := ReadStringFromStream(LDest, -1, IOHandler.DefStringEncoding{$IFDEF STRING_IS_ANSI}, IOHandler.DefAnsiEncoding{$ENDIF});
TIdFTPListResult(FListResult).FDetails := ADetails;
TIdFTPListResult(FListResult).FUsedMLS := False;
// FDirFormat will be updated in ParseFTPList...
finally
FreeAndNil(LDest);
end;
if ADest <> nil then begin
ADest.Assign(FListResult);
end;
DoOnRetrievedDir;
finally
if LTrans <> ftASCII then begin
TransferType := LTrans;
end;
end;
end;
const
AbortedReplies : array [0..5] of Int16 =
(226,426, 450,451,425,550);
//226 was added because one server will return that twice if you aborted
//during an upload.
AcceptableAbortReplies : array [0..8] of Int16 =
(225, 226, 250, 426, 450,451,425,550,552);
//GlobalScape Secure FTP Server returns a 552 for an aborted file
procedure TIdFTP.FinalizeDataOperation;
var
LResponse : Int16;
begin
DoOnDataChannelDestroy;
if FDataChannel <> nil then begin
{$IFNDEF USE_OBJECT_ARC}
FDataChannel.IOHandler.Free;
{$ENDIF}
FDataChannel.IOHandler := nil;
FreeAndNil(FDataChannel);
end;
{
This is a bug fix for servers will do something like this:
[2] Mon 06Jun05 13:33:28 - (000007) PASV
[6] Mon 06Jun05 13:33:28 - (000007) 227 Entering Passive Mode (192,168,1,107,4,22)
[2] Mon 06Jun05 13:33:28 - (000007) RETR test.txt.txt
[6] Mon 06Jun05 13:33:28 - (000007) 550 /test.txt.txt: No such file or directory.
[2] Mon 06Jun05 13:34:28 - (000007) QUIT
[6] Mon 06Jun05 13:34:28 - (000007) 221 Goodbye!
[5] Mon 06Jun05 13:34:28 - (000007) Closing connection for user TEST (00:01:08 connected)
}
if (LastCmdResult.NumericCode div 100) > 2 then
begin
DoStatus(ftpAborted, [RSFTPStatusAbortTransfer]);
Exit;
end;
DoStatus(ftpReady, [RSFTPStatusDoneTransfer]);
// 226 = download successful, 225 = Abort successful}
if FAbortFlag.Value then begin
LResponse := GetResponse(AcceptableAbortReplies);
//Experimental -
if PosInSmallIntArray(LResponse,AbortedReplies) > -1 then begin
GetResponse([226, 225]);
end;
//IMPORTANT!!! KEEP THIS COMMENT!!!
//
//This is a workaround for a problem. When uploading a file on
//one FTP server and aborting that upload, I got this:
//
//Sent 3/9/2005 10:34:58 AM: STOR --------
//Recv 3/9/2005 10:34:58 AM: 150 Opening BINARY mode data connection for [3513]Red_Glas.zip
//Sent 3/9/2005 10:34:59 AM: ABOR
//Recv 3/9/2005 10:35:00 AM: 226 Transfer complete.
//Recv 3/9/2005 10:35:00 AM: 226 Abort successful
//
//but at ftp.ipswitch.com (a WS_FTP Server 5.0.4 (2555009845) server ),
//I was getting this when aborting a download
//
//Sent 3/9/2005 12:43:41 AM: RETR imail6.pdf
//Recv 3/9/2005 12:43:41 AM: 150 Opening BINARY data connection for imail6.pdf (2150082 bytes)
//Sent 3/9/2005 12:43:42 AM: ABOR
//Recv 3/9/2005 12:43:42 AM: 226 abort successful
//Recv 3/9/2005 12:43:43 AM: 425 transfer canceled
//
if LResponse = 226 then begin
if IOHandler.Readable(10) then begin
GetResponse(AbortedReplies);
end;
end;
DoStatus(ftpAborted, [RSFTPStatusAbortTransfer]);
//end experimental section
end else begin
//ftp.marist.edu returns 250
GetResponse([226, 225, 250]);
end;
end;
procedure TIdFTP.InternalPut(const ACommand: string; ASource: TStream;
AFromBeginning: Boolean = True; AResume: Boolean = False);
{$IFNDEF MSWINDOWS}
procedure WriteStreamFromBeginning;
var
LBuffer: TIdBytes;
LBufSize: Integer;
begin
// Copy entire stream without relying on ASource.Size so Unix-to-DOS
// conversion can be done on the fly.
BeginWork(wmWrite, ASource.Size);
try
SetLength(LBuffer, FDataChannel.IOHandler.SendBufferSize);
while True do begin
LBufSize := ASource.Read(LBuffer[0], Length(LBuffer));
if LBufSize > 0 then
FDataChannel.IOHandler.Write(LBuffer, LBufSize)
else
Break;
end;
finally
EndWork(wmWrite);
end;
end;
{$ENDIF}
var
LIP: string;
LPort: TIdPort;
LPasvCl : TIdTCPClient;
LPortSv : TIdSimpleServer;
// under ARC, convert a weak reference to a strong reference before working with it
LCompressor : TIdZLibCompressorBase;
begin
FAbortFlag.Value := False;
LCompressor := nil;
if FCurrentTransferMode = dmDeflate then begin
LCompressor := FCompressor;
if not Assigned(LCompressor) then begin
raise EIdFTPMissingCompressor.Create(RSFTPMissingCompressor);
end;
if not LCompressor.IsReady then begin
raise EIdFTPCompressorNotReady.Create(RSFTPCompressorNotReady);
end;
end;
//for SSL FXP, we have to do it here because there is no command were a client
//submits data through a data port where the SSCN setting is ignored.
ClearSSCN;
DoStatus(ftpTransfer, [RSFTPStatusStartTransfer]);
// try
if FPassive then begin
SendPret(ACommand);
if FUsingExtDataPort then begin
SendEPassive(LIP, LPort);
end else begin
SendPassive(LIP, LPort);
end;
if AResume then begin
Self.SendCmd('REST ' + IntToStr(ASource.Position), [350]); {do not localize}
end;
IOHandler.WriteLn(ACommand);
if Socket <> nil then begin
FDataChannel := TIdTCPClient.Create(nil);
end else begin
FDataChannel := nil;
end;
LPasvCl := TIdTCPClient(FDataChannel);
try
InitDataChannel;
if (Self.Socket <> nil) and PassiveUseControlHost then begin
//Do not use an assignment from Self.Host
//because a DNS name may not resolve to the same
//IP address every time. This is the case where
//the workload is distributed around several servers.
//Besides, we already know the Peer's IP address so
//why waste time querying it.
LIP := Self.Socket.Binding.PeerIP;
end;
if LPasvCl <> nil then begin
LPasvCl.Host := LIP;
LPasvCl.Port := LPort;
DoOnDataChannelCreate;
LPasvCl.Connect;
end;
try
Self.GetResponse([110, 125, 150]);
try
if FDataChannel <> nil then begin
if FUsingSFTP and (FDataPortProtection = ftpdpsPrivate) then begin
TIdSSLIOHandlerSocketBase(FDataChannel.IOHandler).Passthrough := False;
end;
if Assigned(LCompressor) then begin
LCompressor.CompressFTPToIO(ASource, FDataChannel.IOHandler,
FZLibCompressionLevel, FZLibWindowBits, FZLibMemLevel, FZLibStratagy);
end else begin
if AFromBeginning then begin
{$IFNDEF MSWINDOWS}
WriteStreamFromBeginning;
{$ELSE}
FDataChannel.IOHandler.Write(ASource, 0, False); // from beginning
{$ENDIF}
end else begin
FDataChannel.IOHandler.Write(ASource, -1, False); // from current position
end;
end;
end;
except
on E: EIdSocketError do
begin
// If 10038 - abort was called. Server will return 225
if E.LastError <> 10038 then begin
raise;
end;
end;
end;
finally
if LPasvCl <> nil then begin
LPasvCl.Disconnect(False);
end;
end;
finally
FinalizeDataOperation;
end;
end else begin
if Socket <> nil then begin
FDataChannel := TIdSimpleServer.Create(nil);
end else begin
FDataChannel := nil;
end;
LPortSv := TIdSimpleServer(FDataChannel);
try
InitDataChannel;
if LPortSv <> nil then begin
LPortSv.BoundIP := Self.Socket.Binding.IP;
LPortSv.BoundPort := FDataPort;
LPortSv.BoundPortMin := FDataPortMin;
LPortSv.BoundPortMax := FDataPortMax;
DoOnDataChannelCreate;
LPortSv.BeginListen;
if FUsingExtDataPort then begin
SendEPort(LPortSv.Binding);
end else begin
SendPort(LPortSv.Binding);
end;
end else begin
// TODO:
{
if FUsingExtDataPort then begin
SendEPort(?);
end else begin
SendPort(?);
end;
}
end;
if AResume then begin
Self.SendCmd('REST ' + IntToStr(ASource.Position), [350]); {do not localize}
end;
Self.SendCmd(ACommand, [125, 150]);
if LPortSv <> nil then begin
LPortSv.Listen(ListenTimeout);
if FUsingSFTP and (FDataPortProtection = ftpdpsPrivate) then begin
TIdSSLIOHandlerSocketBase(FDataChannel.IOHandler).PassThrough := False;
end;
if Assigned(LCompressor) then begin
LCompressor.CompressFTPToIO(ASource, FDataChannel.IOHandler,
FZLibCompressionLevel, FZLibWindowBits, FZLibMemLevel, FZLibStratagy);
end else begin
if AFromBeginning then begin
{$IFNDEF MSWINDOWS}
WriteStreamFromBeginning;
{$ELSE}
FDataChannel.IOHandler.Write(ASource, 0, False); // from beginning
{$ENDIF}
end else begin
FDataChannel.IOHandler.Write(ASource, -1, False); // from current position
end;
end;
end;
finally
FinalizeDataOperation;
end;
end;
{ This will silently ignore the STOR request if the server has forcibly disconnected
(kicked or timed out) before the request starts
except
//Note that you are likely to get an exception you abort a transfer
//hopefully, this will make things work better.
on E: EIdConnClosedGracefully do begin
end;
end;}
{ commented out because we might need to revert back to this
if new code fails.
if (LResponse = 426) or (LResponse = 450) then
begin
// some servers respond with 226 on ABOR
GetResponse([226, 225]);
DoStatus(ftpAborted, [RSFTPStatusAbortTransfer]);
end;
}
end;
procedure TIdFTP.InternalGet(const ACommand: string; ADest: TStream; AResume: Boolean = false);
var
LIP: string;
LPort: TIdPort;
LPasvCl : TIdTCPClient;
LPortSv : TIdSimpleServer;
// under ARC, convert a weak reference to a strong reference before working with it
LCompressor: TIdZLibCompressorBase;
begin
FAbortFlag.Value := False;
LCompressor := nil;
if FCurrentTransferMode = dmDeflate then begin
LCompressor := FCompressor;
if not Assigned(LCompressor) then begin
raise EIdFTPMissingCompressor.Create(RSFTPMissingCompressor);
end;
if not LCompressor.IsReady then begin
raise EIdFTPCompressorNotReady.Create(RSFTPCompressorNotReady);
end;
end;
DoStatus(ftpTransfer, [RSFTPStatusStartTransfer]);
if FPassive then begin
SendPret(ACommand);
//PASV or EPSV
if FUsingExtDataPort then begin
SendEPassive(LIP, LPort);
end else begin
SendPassive(LIP, LPort);
end;
if Socket <> nil then begin
FDataChannel := TIdTCPClient.Create(nil);
end else begin
FDataChannel := nil;
end;
LPasvCl := TIdTCPClient(FDataChannel);
try
InitDataChannel;
if (Self.Socket <> nil) and PassiveUseControlHost then begin
//Do not use an assignment from Self.Host
//because a DNS name may not resolve to the same
//IP address every time. This is the case where
//the workload is distributed around several servers.
//Besides, we already know the Peer's IP address so
//why waste time querying it.
LIP := Self.Socket.Binding.PeerIP;
end;
if LPasvCl <> nil then begin
LPasvCl.Host := LIP;
LPasvCl.Port := LPort;
DoOnDataChannelCreate;
LPasvCl.Connect;
end;
try
if AResume then begin
Self.SendCmd('REST ' + IntToStr(ADest.Position), [350]); {do not localize}
end;
// APR: Ericsson Switch FTP
//
// RLebeau: some servers send 450 when no files are
// present, so do not read the stream in that case
if Self.SendCmd(ACommand, [125, 150, 154, 450]) <> 450 then
begin
if LPasvCl <> nil then begin
if FUsingSFTP and (FDataPortProtection = ftpdpsPrivate) then begin
TIdSSLIOHandlerSocketBase(FDataChannel.IOHandler).Passthrough := False;
end;
if Assigned(LCompressor) then begin
LCompressor.DecompressFTPFromIO(LPasvCl.IOHandler, ADest, FZLibWindowBits);
end else begin
LPasvCl.IOHandler.ReadStream(ADest, -1, True);
end;
end;
end;
finally
if LPasvCl <> nil then begin
LPasvCl.Disconnect(False);
end;
end;
finally
FinalizeDataOperation;
end;
end else begin
// PORT or EPRT
if Socket <> nil then begin
FDataChannel := TIdSimpleServer.Create(nil);
end else begin
FDataChannel := nil;
end;
LPortSv := TIdSimpleServer(FDataChannel);
try
InitDataChannel;
if LPortSv <> nil then begin
LPortSv.BoundIP := Self.Socket.Binding.IP;
LPortSv.BoundPort := FDataPort;
LPortSv.BoundPortMin := FDataPortMin;
LPortSv.BoundPortMax := FDataPortMax;
DoOnDataChannelCreate;
LPortSv.BeginListen;
if FUsingExtDataPort then begin
SendEPort(LPortSv.Binding);
end else begin
SendPort(LPortSv.Binding);
end;
end else begin
// TODO:
{
if FUsingExtDataPort then begin
SendEPort(?);
end else begin
SendPort(?);
end;
}
end;
if AResume then begin
SendCmd('REST ' + IntToStr(ADest.Position), [350]); {do not localize}
end;
SendCmd(ACommand, [125, 150, 154]); //APR: Ericsson Switch FTP);
if LPortSv <> nil then begin
LPortSv.Listen(ListenTimeout);
if FUsingSFTP and (FDataPortProtection = ftpdpsPrivate) then begin
TIdSSLIOHandlerSocketBase(FDataChannel.IOHandler).PassThrough := False;
end;
if Assigned(LCompressor) then begin
LCompressor.DecompressFTPFromIO(LPortSv.IOHandler, ADest, FZLibWindowBits);
end else begin
FDataChannel.IOHandler.ReadStream(ADest, -1, True);
end;
end;
finally
FinalizeDataOperation;
end;
end;
// ToDo: Change that to properly handle response code (not just success or except)
// 226 = download successful, 225 = Abort successful}
//commented out in case we need to revert back to this.
{ LResponse := GetResponse([225, 226, 250, 426, 450]);
if (LResponse = 426) or (LResponse = 450) then begin
GetResponse([226, 225]);
DoStatus(ftpAborted, [RSFTPStatusAbortTransfer]);
end; }
end;
procedure TIdFTP.DoOnDataChannelCreate;
begin
// While the Control Channel is idle, Enable/disable TCP/IP keepalives.
// They're very small (40-byte) packages and will be sent every
// NATKeepAlive.IntervalMS after the connection has been idle for
// NATKeepAlive.IdleTimeMS. Prior to Windows 2000, the idle and
// timeout values are system wide and have to be set in the registry;
// the default is idle = 2 hours, interval = 1 second.
if (Socket <> nil) and NATKeepAlive.UseKeepAlive then begin
Socket.Binding.SetKeepAliveValues(True, NATKeepAlive.IdleTimeMS, NATKeepAlive.IntervalMS);
end;
if Assigned(FOnDataChannelCreate) then begin
OnDataChannelCreate(Self, FDataChannel);
end;
end;
procedure TIdFTP.DoOnDataChannelDestroy;
begin
if Assigned(FOnDataChannelDestroy) then begin
OnDataChannelDestroy(Self, FDataChannel);
end;
if (Socket <> nil) and NATKeepAlive.UseKeepAlive then begin
Socket.Binding.SetKeepAliveValues(False, 0, 0);
end;
end;
procedure TIdFTP.SetNATKeepAlive(AValue: TIdFTPKeepAlive);
begin
FNATKeepAlive.Assign(AValue);
end;
{ TIdFtpKeepAlive }
procedure TIdFtpKeepAlive.Assign(Source: TPersistent);
var
LSource: TIdFTPKeepAlive;
begin
if Source is TIdFTPKeepAlive then begin
LSource := TIdFTPKeepAlive(Source);
FUseKeepAlive := LSource.UseKeepAlive;
FIdleTimeMS := LSource.IdleTimeMS;
FIntervalMS := LSource.IntervalMS;
end else begin
inherited Assign(Source);
end;
end;
procedure TIdFTP.DisconnectNotifyPeer;
begin
if IOHandler.Connected then begin
IOHandler.WriteLn('QUIT'); {do not localize}
IOHandler.CheckForDataOnSource(100);
if not IOHandler.InputBufferIsEmpty then begin
GetInternalResponse;
end;
end;
end;
{$I IdDeprecatedImplBugOff.inc}
procedure TIdFTP.Quit;
{$I IdDeprecatedImplBugOn.inc}
begin
Disconnect;
end;
procedure TIdFTP.KillDataChannel;
begin
// Had kill the data channel ()
if Assigned(FDataChannel) then begin
FDataChannel.Disconnect(False); //FDataChannel.IOHandler.DisconnectSocket; {//BGO}
end;
end;
// IMPORTANT!!! THis is for later reference.
//
// Note that we do not send the Telnet IP and Sync as suggestedc by RFC 959.
// We do not do so because some servers will mistakenly assume that the sequences
// are part of the command and than give a syntax error.
// I noticed this with FTPSERVE IBM VM Level 510, Microsoft FTP Service (Version 5.0),
// GlobalSCAPE Secure FTP Server (v. 2.0), and Pure-FTPd [privsep] [TLS].
//
// Thus, I feel that sending sequences is just going to aggravate this situation.
// It is probably the reason why some FTP clients no longer are sending Telnet IP
// and Sync with the ABOR command.
procedure TIdFTP.Abort;
begin
// only send the abort command. The Data channel is supposed to disconnect
if Connected then begin
IOHandler.WriteLn('ABOR'); {do not localize}
end;
// Kill the data channel: usually, the server doesn't close it by itself
KillDataChannel;
if Assigned(FDataChannel) then begin
FAbortFlag.Value := True;
end else begin
GetResponse([]);
end;
end;
procedure TIdFTP.SendPort(AHandle: TIdSocketHandle);
begin
if FExternalIP <> '' then begin
SendPort(FExternalIP, AHandle.Port);
end else begin
SendPort(AHandle.IP, AHandle.Port);
end;
end;
procedure TIdFTP.SendPort(const AIP: String; const APort: TIdPort);
begin
SendDataSettings;
SendCmd('PORT ' + ReplaceAll(AIP, '.', ',') {do not localize}
+ ',' + IntToStr(APort div 256) + ',' + IntToStr(APort mod 256), [200]); {do not localize}
end;
procedure TIdFTP.InitDataChannel;
var
LSSL : TIdSSLIOHandlerSocketBase;
begin
if FDataChannel = nil then begin
Exit;
end;
if FDataPortProtection = ftpdpsPrivate then begin
LSSL := TIdSSLIOHandlerSocketBase(IOHandler);
FDataChannel.IOHandler := LSSL.Clone;
//we have to delay the actual negotiation until we get the reply and
//and just before the readString
TIdSSLIOHandlerSocketBase(FDataChannel.IOHandler).Passthrough := True;
end else begin
FDataChannel.IOHandler := TIdIOHandler.MakeDefaultIOHandler(Self);
end;
if FDataChannel is TIdTCPClient then
begin
TIdTCPClient(FDataChannel).IPVersion := IPVersion;
TIdTCPClient(FDataChannel).ReadTimeout := FTransferTimeout;
//Now SocksInfo are multi-thread safe
FDataChannel.IOHandler.ConnectTimeout := IOHandler.ConnectTimeout;
end
else if FDataChannel is TIdSimpleServer then
begin
TIdSimpleServer(FDataChannel).IPVersion := IPVersion;
end;
if Assigned(FDataChannel.Socket) and Assigned(Socket) then
begin
FDataChannel.Socket.TransparentProxy := Socket.TransparentProxy;
end;
FDataChannel.IOHandler.ReadTimeout := FTransferTimeout;
FDataChannel.IOHandler.SendBufferSize := IOHandler.SendBufferSize;
FDataChannel.IOHandler.RecvBufferSize := IOHandler.RecvBufferSize;
FDataChannel.IOHandler.LargeStream := True;
// FDataChannel.IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
// FDataChannel.IOHandler.DefAnsiEncoding := IndyTextEncoding_OSDefault;
FDataChannel.WorkTarget := Self;
end;
procedure TIdFTP.Put(const ASource: TStream; const ADestFile: string;
const AAppend: Boolean = False; const AStartPos: TIdStreamSize = -1);
begin
if ADestFile = '' then begin
raise EIdFTPUploadFileNameCanNotBeEmpty.Create(RSFTPFileNameCanNotBeEmpty);
end;
if AStartPos > -1 then begin
ASource.Position := AStartPos;
end;
DoBeforePut(ASource); //APR);
if AAppend then begin
InternalPut('APPE ' + ADestFile, ASource, False, False); {Do not localize}
end else begin
InternalPut('STOR ' + ADestFile, ASource, AStartPos = -1, AStartPos > -1); {Do not localize}
end;
DoAfterPut;
end;
procedure TIdFTP.Put(const ASourceFile: string; const ADestFile: string = '';
const AAppend: Boolean = False; const AStartPos: TIdStreamSize = -1);
var
LSourceStream: TStream;
LDestFileName : String;
begin
LDestFileName := ADestFile;
if LDestFileName = '' then begin
LDestFileName := ExtractFileName(ASourceFile);
end;
LSourceStream := TIdReadFileNonExclusiveStream.Create(ASourceFile);
try
Put(LSourceStream, LDestFileName, AAppend, AStartPos);
finally
FreeAndNil(LSourceStream);
end;
end;
procedure TIdFTP.StoreUnique(const ASource: TStream; const AStartPos: TIdStreamSize = -1);
begin
if AStartPos > -1 then begin
ASource.Position := AStartPos;
end;
DoBeforePut(ASource);
InternalPut('STOU', ASource, AStartPos = -1, False); {Do not localize}
DoAfterPut;
end;
procedure TIdFTP.StoreUnique(const ASourceFile: string; const AStartPos: TIdStreamSize = -1);
var
LSourceStream: TStream;
begin
LSourceStream := TIdReadFileExclusiveStream.Create(ASourceFile);
try
StoreUnique(LSourceStream, AStartPos);
finally
FreeAndNil(LSourceStream);
end;
end;
procedure TIdFTP.SendInternalPassive(const ACmd: String; var VIP: string;
var VPort: TIdPort);
function IsRoutableAddress(AIP: string): Boolean;
begin
Result := not TextStartsWith(AIP, '127') and // Loopback 127.0.0.0-127.255.255.255
not TextStartsWith(AIP, '10.') and // Private 10.0.0.0-10.255.255.255
not TextStartsWith(AIP, '169.254') and // Link-local 169.254.0.0-169.254.255.255
not TextStartsWith(AIP, '192.168') and // Private 192.168.0.0-192.168.255.255
not (TextStartsWith(AIP, '172') and (AIP[7] = '.') and // Private 172.16.0.0-172.31.255.255
(IndyStrToInt(Copy(AIP, 5, 2)) in [16..31]))
end;
var
i, bLeft, bRight: integer;
s: string;
begin
SendDataSettings;
SendCmd(ACmd, 227); {do not localize}
s := Trim(LastCmdResult.Text[0]);
// Case 1 (Normal)
// 227 Entering passive mode(100,1,1,1,23,45)
bLeft := IndyPos('(', s); {do not localize}
bRight := IndyPos(')', s); {do not localize}
// Microsoft FTP Service may include a leading ( but not a trailing ),
// so handle any combination of "(..)", "(..", "..)", and ".."
if bLeft = 0 then bLeft := RPos(#32, S);
if bRight = 0 then bRight := Length(S) + 1;
S := Copy(S, bLeft + 1, bRight - bLeft - 1);
VIP := ''; {do not localize}
for i := 1 to 4 do begin
VIP := VIP + '.' + Fetch(s, ','); {do not localize}
end;
IdDelete(VIP, 1, 1);
// Server sent an unroutable address (private/reserved/etc). Use the IP we
// connected to instead
if not IsRoutableAddress(VIP) and IsRoutableAddress(Socket.Binding.PeerIP) then begin
VIP := Socket.Binding.PeerIP;
end;
// Determine port
VPort := TIdPort(IndyStrToInt(Fetch(s, ',')) and $FF) shl 8; {do not localize}
//use trim as one server sends something like this:
//"227 Passive mode OK (195,92,195,164,4,99 )"
VPort := VPort or TIdPort(IndyStrToInt(Fetch(s, ',')) and $FF); {Do not translate}
end;
procedure TIdFTP.SendPassive(var VIP: string; var VPort: TIdPort);
begin
SendInternalPassive('PASV', VIP, VPort); {do not localize}
end;
procedure TIdFTP.SendCPassive(var VIP: string; var VPort: TIdPort);
begin
SendInternalPassive('CPSV', VIP, VPort); {do not localize}
end;
procedure TIdFTP.Noop;
begin
SendCmd('NOOP', 200); {do not localize}
end;
procedure TIdFTP.MakeDir(const ADirName: string);
begin
SendCmd('MKD ' + ADirName, 257); {do not localize}
end;
function TIdFTP.RetrieveCurrentDir: string;
begin
SendCmd('PWD', 257); {do not localize}
Result := LastCmdResult.Text[0];
IdDelete(Result, 1, IndyPos('"', Result)); // Remove first doublequote {do not localize}
Result := Copy(Result, 1, IndyPos('"', Result) - 1); // Remove anything from second doublequote {do not localize} // to end of line
// TODO: handle embedded quotation marks. RFC 959 allows them to be present
end;
procedure TIdFTP.RemoveDir(const ADirName: string);
begin
SendCmd('RMD ' + ADirName, 250); {do not localize}
end;
procedure TIdFTP.Delete(const AFilename: string);
begin
// Linksys NSLU2 NAS returns 200, Ultimodule IDAL returns 257
SendCmd('DELE ' + AFilename, [200, 250, 257]); {do not localize}
end;
(*
CHANGE WORKING DIRECTORY (CWD)
This command allows the user to work with a different
directory or dataset for file storage or retrieval without
altering his login or accounting information. Transfer
parameters are similarly unchanged. The argument is a
pathname specifying a directory or other system dependent
file group designator.
CWD
250
500, 501, 502, 421, 530, 550
*)
procedure TIdFTP.ChangeDir(const ADirName: string);
begin
SendCmd('CWD ' + ADirName, [200, 250, 257]); //APR: Ericsson Switch FTP {do not localize}
end;
(*
CHANGE TO PARENT DIRECTORY (CDUP)
This command is a special case of CWD, and is included to
simplify the implementation of programs for transferring
directory trees between operating systems having different
syntaxes for naming the parent directory. The reply codes
shall be identical to the reply codes of CWD. See
Appendix II for further details.
CDUP
200
500, 501, 502, 421, 530, 550
*)
procedure TIdFTP.ChangeDirUp;
begin
// RFC lists 200 as the proper response, but in another section says that it can return the
// same as CWD, which expects 250. That is it contradicts itself.
// MS in their infinite wisdom chnaged IIS 5 FTP to return 250.
SendCmd('CDUP', [200, 250]); {do not localize}
end;
procedure TIdFTP.Site(const ACommand: string);
begin
SendCmd('SITE ' + ACommand, 200); {do not localize}
end;
procedure TIdFTP.Rename(const ASourceFile, ADestFile: string);
begin
SendCmd('RNFR ' + ASourceFile, 350); {do not localize}
SendCmd('RNTO ' + ADestFile, 250); {do not localize}
end;
function TIdFTP.Size(const AFileName: String): Int64;
var
LTrans : TIdFTPTransferType;
SizeStr: String;
begin
Result := -1;
// RLebeau 03/13/2009: some servers refuse to accept the SIZE command in
// ASCII mode, returning a "550 SIZE not allowed in ASCII mode" reply.
// We put the connection in BINARY mode, even though no data connection is
// actually being used. We restore it if the original mode was not BINARY.
// It's a good idea to do this anyway because some other clients do this
// as well.
LTrans := TransferType;
if LTrans <> ftBinary then begin
Self.TransferType := ftBinary;
end;
try
if SendCmd('SIZE ' + AFileName) = 213 then begin {do not localize}
SizeStr := Trim(LastCmdResult.Text.Text);
IdDelete(SizeStr, 1, IndyPos(' ', SizeStr)); // delete the response {do not localize}
Result := IndyStrToInt64(SizeStr, -1);
end;
finally
if LTrans <> ftBinary then begin
TransferType := LTrans;
end;
end;
end;
//Added by SP
procedure TIdFTP.ReInitialize(ADelay: UInt32 = 10);
begin
IndySleep(ADelay); //Added
if SendCmd('REIN', [120, 220, 500]) <> 500 then begin {do not localize}
FLoginMsg.Clear;
FCanResume := False;
if Assigned(FDirectoryListing) then begin
FDirectoryListing.Clear;
end;
FUsername := ''; {do not localize}
FPassword := ''; {do not localize}
FPassive := Id_TIdFTP_Passive;
FCanResume := False;
FResumeTested := False;
FSystemDesc := '';
FTransferType := Id_TIdFTP_TransferType;
IOHandler.DefStringEncoding := IndyTextEncoding_8Bit;
{$IFDEF STRING_IS_ANSI}
IOHandler.DefAnsiEncoding := IndyTextEncoding_OSDefault;
{$ENDIF}
if FUsingSFTP and (FUseTLS <> utUseImplicitTLS) then begin
(IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := True;
FUsingSFTP := False;
FUseCCC := False;
end;
end;
end;
procedure TIdFTP.Allocate(AAllocateBytes: Integer);
begin
SendCmd('ALLO ' + IntToStr(AAllocateBytes), [200]); {do not localize}
end;
procedure TIdFTP.Status(AStatusList: TStrings);
begin
if SendCmd('STAT', [211, 212, 213, 500]) <> 500 then begin {do not localize}
AStatusList.Text := LastCmdResult.Text.Text;
end;
end;
procedure TIdFTP.Help(AHelpContents: TStrings; ACommand: String = ''); {do not localize}
begin
if SendCmd(Trim('HELP ' + ACommand), [211, 214, 500]) <> 500 then begin {do not localize}
AHelpContents.Text := LastCmdResult.Text.Text;
end;
end;
function TIdFTP.CheckAccount: Boolean;
begin
if (FAccount = '') and Assigned(FOnNeedAccount) then begin
FOnNeedAccount(Self, FAccount);
end;
Result := FAccount <> '';
end;
procedure TIdFTP.StructureMount(APath: String);
begin
SendCmd('SMNT ' + APath, [202, 250, 500]); {do not localize}
end;
procedure TIdFTP.FileStructure(AStructure: TIdFTPDataStructure);
const
StructureTypes: array[TIdFTPDataStructure] of String = ('F', 'R', 'P'); {do not localize}
begin
SendCmd('STRU ' + StructureTypes[AStructure], [200, 500]); {do not localize}
{ TODO: Needs to be finished }
end;
procedure TIdFTP.TransferMode(ATransferMode: TIdFTPTransferMode);
var
s: String;
begin
if FCurrentTransferMode <> ATransferMode then begin
s := '';
case ATransferMode of
// dmBlock: begin
// s := 'B'; {do not localize}
// end;
// dmCompressed: begin
// s := 'C'; {do not localize}
// end;
dmStream: begin
s := 'S'; {do not localize}
end;
dmDeflate: begin
if not Assigned(FCompressor) then begin
raise EIdFTPMissingCompressor.Create(RSFTPMissingCompressor);
end;
if Self.IsCompressionSupported then begin
s := 'Z'; {Do not localize}
end;
end;
end;
if s = '' then begin
raise EIdFTPUnsupportedTransferMode.Create(RSFTPUnsupportedTransferMode);
end;
SendCmd('MODE ' + s, 200); {do not localize}
FCurrentTransferMode := ATransferMode;
end;
end;
destructor TIdFTP.Destroy;
begin
FreeAndNil(FClientInfo);
FreeAndNil(FListResult);
FreeAndNil(FLoginMsg);
FreeAndNil(FDirectoryListing);
FreeAndNil(FLangsSupported);
FreeAndNil(FProxySettings); //APR
FreeAndNil(FTZInfo);
FreeAndNil(FAbortFlag);
FreeAndNil(FNATKeepAlive);
inherited Destroy;
end;
function TIdFTP.Quote(const ACommand: String): Int16;
begin
Result := SendCmd(ACommand);
end;
procedure TIdFTP.IssueFEAT;
var
LClnt: String;
LBuf : String;
i : Integer;
begin
//Feat data
SendCmd('FEAT'); {do not localize}
FCapabilities.Clear;
//Ipswitch's FTP WS-FTP Server may issue 221 as success
if LastCmdResult.NumericCode in [211,221] then begin
FCapabilities.AddStrings(LastCmdResult.Text);
//we remove the first and last lines because we only want the list
if FCapabilities.Count > 0 then begin
FCapabilities.Delete(0);
end;
if FCapabilities.Count > 0 then begin
FCapabilities.Delete(FCapabilities.Count-1);
end;
end;
if FUsingExtDataPort then begin
FUsingExtDataPort := IsExtSupported('EPRT') and IsExtSupported('EPSV'); {do not localize}
end;
FCanUseMLS := IsExtSupported('MLSD') or IsExtSupported('MLST'); {do not localize}
ExtractFeatFacts('LANG', FLangsSupported); {do not localize}
//see if compression is supported.
//we parse this way because IxExtensionSupported can only work
//with one word.
FIsCompressionSupported := False;
for i := 0 to FCapabilities.Count-1 do begin
LBuf := Trim(FCapabilities[i]);
if LBuf = 'MODE Z' then begin {do not localize}
FIsCompressionSupported := True;
Break;
end;
end;
// send the CLNT command before sending the OPTS UTF8 command.
// some servers need this in order to work around a bug in
// Microsoft Internet Explorer's UTF-8 handling
if IsExtSupported('CLNT') then begin {do not localize}
LClnt := FClientInfo.ClntOutput;
if LClnt = '' then begin
LClnt := gsIdProductName + ' ' + gsIdVersion;
end;
SendCmd('CLNT ' + LClnt); {do not localize}
end;
if IsExtSupported('UTF8') then begin {do not localize}
// RLebeau 10/1/13: per RFC 2640, OPTS commands are no longer used to
// activate UTF-8. If the server reports the 'UTF8' capability, it is
// required to detect and accept UTF-8 encoded paths/filenames...
{
// trying non-standard UTF-8 extension first, many servers use this...
// Cerberus and RaidenFTP return 220, but TitanFTP and Gene6 return 200 instead...
if not SendCmd('OPTS UTF8 ON') in [200, 220] then begin {do not localize
// trying draft-ietf-ftpext-utf-8-option-00.txt next...
if SendCmd('OPTS UTF-8 NLST') <> 200 then begin {do not localize
Exit;
end;
end;
}
IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
end;
procedure TIdFTP.Login;
var
i : Integer;
LResp : Word;
LCmd : String;
function FtpHost: String;
begin
if FPort = IDPORT_FTP then begin
Result := FHost;
end else begin
Result := FHost + Id_TIdFTP_HostPortDelimiter + IntToStr(FPort);
end;
end;
begin
//This has to be here because the Rein command clears encryption.
//RFC 4217
//TLS part
FUsingSFTP := False;
if UseTLS in ExplicitTLSVals then begin
if FAUTHCmd = tAuto then begin
{Note that we can not call SupportsTLS at all. That depends upon the FEAT response
and unfortunately, some servers such as WS_FTP Server 4.0.0 (78162662)
will not accept a FEAT command until you login. In other words, you have to do
this by trial and error.
}
//334 has to be accepted because of a broekn implementation
//see: http://www.ford-hutchinson.com/~fh-1-pfh/ftps-ext.html#bad
{Note that we have to try several commands because some servers use AUTH TLS while others use
AUTH SSL. GlobalScape's FTP Server only uses AUTH SSL while IpSwitch's uses AUTH TLS (the correct behavior).
We try two other commands for historical reasons.
}
for i := 0 to 3 do begin
LResp := SendCmd('AUTH ' + TLS_AUTH_NAMES[i]); {do not localize}
if (LResp = 234) or (LResp = 334) then begin
//okay. do the handshake
TLSHandshake;
FUsingSFTP := True;
//we are done with the negotiation, let's close this.
Break;
end;
//see if the error was not any type of syntax error code
//if it wasn't, we fail the command.
if (LResp div 500) <> 1 then begin
ProcessTLSNegCmdFailed;
Break;
end;
end;
end else begin
LResp := SendCmd('AUTH ' + TLS_AUTH_NAMES[Ord(FAUTHCmd)-1]); {do not localize}
if (LResp = 234) or (LResp = 334) then begin
//okay. do the handshake
TLSHandshake;
FUsingSFTP := True;
end else begin
ProcessTLSNegCmdFailed;
end;
end;
end;
// TODO: should this be moved inside the 'if UseTLS in ExplicitTLSVals' block?
if not FUsingSFTP then begin
ProcessTLSNotAvail;
end;
//login
case ProxySettings.ProxyType of
fpcmNone:
begin
LCmd := MakeXAUTCmd( Greeting.Text.Text , FUserName, GetLoginPassword);
if (LCmd <> '') and (not GetFIPSMode ) then begin
if SendCmd(LCmd, [230, 232, 331]) = 331 then begin {do not localize}
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end else begin
if SendCmd('USER ' + FUserName, [230, 232, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + GetLoginPassword, [230, 332]); {do not localize}
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
end;
fpcmUserSite:
begin
//This also supports WinProxy
if Length(ProxySettings.UserName) > 0 then begin
if SendCmd('USER ' + ProxySettings.UserName, [230, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + ProxySettings.Password, 230); {do not localize}
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
if SendCmd('USER ' + FUserName + '@' + FtpHost, [230, 232, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + GetLoginPassword, [230, 331]); {do not localize}
if IsAccountNeeded then
begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
fpcmSite:
begin
if Length(ProxySettings.UserName) > 0 then begin
if SendCmd('USER ' + ProxySettings.UserName, [230, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + ProxySettings.Password, 230); {do not localize}
end;
end;
SendCmd('SITE ' + FtpHost); // ? Server Reply? 220? {do not localize}
if SendCmd('USER ' + FUserName, [230, 232, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + GetLoginPassword, [230, 332]); {do not localize}
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
fpcmOpen:
begin
if Length(ProxySettings.UserName) > 0 then begin
if SendCmd('USER ' + ProxySettings.UserName, [230, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + GetLoginPassword, [230, 332]); {do not localize}
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
SendCmd('OPEN ' + FtpHost);//? Server Reply? 220? {do not localize}
if SendCmd('USER ' + FUserName, [230, 232, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + GetLoginPassword, [230, 332]); {do not localize}
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
fpcmUserPass: //USER user@firewalluser@hostname / PASS pass@firewallpass
begin
if SendCmd(IndyFormat('USER %s@%s@%s',
[FUserName, ProxySettings.UserName, FtpHost]), [230, 232, 331]) = 331 then begin {do not localize}
if Length(ProxySettings.Password) > 0 then begin
SendCmd('PASS ' + GetLoginPassword + '@' + ProxySettings.Password, [230, 332]); {do not localize}
end else begin
//// needs otp ////
SendCmd('PASS ' + GetLoginPassword, [230,332]); {do not localize}
end;
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
fpcmTransparent:
begin
//I think fpcmTransparent means to connect to the regular host and the firewalll
//intercepts the login information.
if Length(ProxySettings.UserName) > 0 then begin
if SendCmd('USER ' + ProxySettings.UserName, [230, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + ProxySettings.Password, [230,332]); {do not localize}
end;
end;
if SendCmd('USER ' + FUserName, [230, 232, 331]) = 331 then begin {do not localize}
SendCmd('PASS ' + GetLoginPassword, [230,332]); {do not localize}
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]);
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
fpcmUserHostFireWallID : //USER hostuserId@hostname firewallUsername
begin
if SendCmd(Trim('USER ' + Username + '@' + FtpHost + ' ' + ProxySettings.UserName), [230, 331]) = 331 then begin {do not localize}
if SendCmd('PASS ' + GetLoginPassword, [230,232,202,332]) = 332 then begin
SendCmd('ACCT ' + ProxySettings.Password, [230,232,332]);
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
end;
fpcmNovellBorder : //Novell Border PRoxy
begin
{Done like this:
USER ProxyUserName$ DestFTPUserName$DestFTPHostName
PASS UsereDirectoryPassword$ DestFTPPassword
Novell BorderManager 3.8 Proxy and Firewall Overview and Planning Guide
Copyright <20> 1997-1998, 2001, 2002-2003, 2004 Novell, Inc. All rights reserved.
===
From a WS-FTP Pro firescript at:
http://support.ipswitch.com/kb/WS-20050315-DM01.htm
send ("USER %FwUserId$%HostUserId$%HostAddress")
//send ("PASS %FwPassword$%HostPassword")
}
if SendCmd(Trim('USER ' + ProxySettings.UserName + '$' + Username + '$' + FtpHost), [230, 331]) = 331 then begin {do not localize}
if SendCmd('PASS ' + ProxySettings.UserName + '$' + GetLoginPassword, [230,232,202,332]) = 332 then begin
if IsAccountNeeded then begin
if CheckAccount then begin
SendCmd('ACCT ' + FAccount, [202, 230, 500]); {do not localize}
end else begin
RaiseExceptionForLastCmdResult;
end;
end;
end;
end;
end;
fpcmHttpProxyWithFtp :
begin
{GET ftp://XXX:YYY@indy.nevrona.com/ HTTP/1.0
Host: indy.nevrona.com
User-Agent: Mozilla/4.0 (compatible; Wincmd; Windows NT)
Proxy-Authorization: Basic B64EncodedUserPass==
Connection: close}
raise EIdSocksServerCommandError.Create(RSSocksServerCommandError);
end;//fpcmHttpProxyWithFtp
fpcmCustomProxy :
begin
DoCustomFTPProxy;
end;
end;//case
FLoginMsg.Assign(LastCmdResult);
DoOnBannerAfterLogin(FLoginMsg.FormattedReply);
//should be here because this can be issued more than once per connection.
if FAutoIssueFEAT then begin
IssueFEAT;
end;
SendTransferType(FTransferType);
end;
procedure TIdFTP.DoAfterLogin;
begin
if Assigned(FOnAfterClientLogin) then begin
OnAfterClientLogin(Self);
end;
end;
procedure TIdFTP.DoFTPList;
begin
if Assigned(FOnCreateFTPList) then begin
FOnCreateFTPList(Self, FDirectoryListing);
end;
end;
function TIdFTP.GetDirectoryListing: TIdFTPListItems;
begin
if FDirectoryListing = nil then begin
if Assigned(FOnDirParseStart) then begin
FOnDirParseStart(Self);
end;
ConstructDirListing;
ParseFTPList;
end;
Result := FDirectoryListing;
end;
procedure TIdFTP.SetProxySettings(const Value: TIdFtpProxySettings);
begin
FProxySettings.Assign(Value);
end;
{ TIdFtpProxySettings }
procedure TIdFtpProxySettings.Assign(Source: TPersistent);
var
LSource: TIdFtpProxySettings;
begin
if Source is TIdFtpProxySettings then begin
LSource := TIdFtpProxySettings(Source);
FProxyType := LSource.ProxyType;
FHost := LSource.Host;
FUserName := LSource.UserName;
FPassword := LSource.Password;
FPort := LSource.Port;
end else begin
inherited Assign(Source);
end;
end;
procedure TIdFTP.SendPBSZ;
begin
{NOte that PBSZ - protection buffer size must always be zero for FTP TLS}
if FUsingSFTP or (FUseTLS = utUseImplicitTLS) then begin
//protection buffer size
SendCmd('PBSZ 0'); {do not localize}
end;
end;
procedure TIdFTP.SendPROT;
begin
case FDataPortProtection of
ftpdpsClear : SendCmd('PROT C', 200); //'C' - Clear - neither Integrity nor Privacy {do not localize}
// NOT USED - 'S' - Safe - Integrity without Privacy
// NOT USED - 'E' - Confidential - Privacy without Integrity
// 'P' - Private - Integrity and Privacy
ftpdpsPrivate : SendCmd('PROT P', 200); {do not localize}
end;
end;
procedure TIdFTP.SendDataSettings;
begin
if FUsingSFTP then begin
if not FDataSettingsSent then begin
FDataSettingsSent := True;
SendPBSZ;
SendPROT;
if FUseCCC then begin
FUsingCCC := (SendCmd('CCC') div 100) = 2; {do not localize}
if FUsingCCC then begin
(IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := True;
end;
end;
end;
end;
end;
procedure TIdFTP.SetIOHandler(AValue: TIdIOHandler);
begin
inherited SetIOHandler(AValue);
// UseExtensionDataPort must be true for IPv6 connections.
// PORT and PASV can not communicate IPv6 Addresses
if Socket <> nil then begin
if Socket.IPVersion = Id_IPv6 then begin
FUseExtensionDataPort := True;
end;
end;
end;
procedure TIdFTP.SetUseExtensionDataPort(const AValue: Boolean);
begin
if (not AValue) and (IPVersion = Id_IPv6) then begin
raise EIdFTPMustUseExtWithIPv6.Create(RSFTPMustUseExtWithIPv6);
end;
if TryNATFastTrack then begin
raise EIdFTPMustUseExtWithNATFastTrack.Create(RSFTPMustUseExtWithNATFastTrack);
end;
FUseExtensionDataPort := AValue;
end;
procedure TIdFTP.ParseEPSV(const AReply : String; var VIP : String; var VPort : TIdPort);
var
bLeft, bRight, LPort: Integer;
delim : Char;
s : String;
begin
s := Trim(AReply);
// "229 Entering Extended Passive Mode (|||59028|)"
bLeft := IndyPos('(', s); {do not localize}
bRight := IndyPos(')', s); {do not localize}
s := Copy(s, bLeft + 1, bRight - bLeft - 1);
delim := s[1]; // normally is | but the RFC say it may be different
Fetch(S, delim);
Fetch(S, delim);
VIP := Fetch(S, delim);
if VIP = '' then begin
VIP := Host;
end;
s := Trim(Fetch(S, delim));
LPort := IndyStrToInt(s, 0);
if (LPort < 1) or (LPort > 65535) then begin
raise EIdFTPServerSentInvalidPort.CreateFmt(RSFTPServerSentInvalidPort, [s]);
end;
VPort := TIdPort(LPort and $FFFF);
end;
procedure TIdFTP.SendEPassive(var VIP: string; var VPort: TIdPort);
begin
SendDataSettings;
//Note that for FTP Proxies, it is not desirable for the server to choose
//the EPSV data port IP connection type. We try to if we can.
if FProxySettings.ProxyType <> fpcmNone then begin
if SendCMD('EPSV ' + cIPVersions[IPVersion]) <> 229 then begin {do not localize}
//Raidon and maybe a few others may honor EPSV but not with the proto numbers
SendCMD('EPSV'); {do not localize}
end;
end else begin
SendCMD('EPSV'); {do not localize}
end;
if LastCmdResult.NumericCode <> 229 then begin
SendPassive(VIP, VPort);
FUsingExtDataPort := False;
Exit;
end;
try
ParseEPSV(LastCmdResult.Text[0], VIP, VPort);
except
SendCmd('ABOR'); {do not localize}
raise;
end;
end;
procedure TIdFTP.SendEPort(AHandle: TIdSocketHandle);
begin
SendDataSettings;
if FExternalIP <> '' then begin
SendEPort(FExternalIP, AHandle.Port, AHandle.IPVersion);
end else begin
SendEPort(AHandle.IP, AHandle.Port, AHandle.IPVersion);
end;
end;
procedure TIdFTP.SendEPort(const AIP: String; const APort: TIdPort; const AIPVersion: TIdIPVersion);
begin
if SendCmd('EPRT |' + cIPVersions[AIPVersion] + '|' + AIP + '|' + IntToStr(APort) + '|') <> 200 then begin {do not localize}
SendPort(AIP, APort);
FUsingExtDataPort := False;
end;
end;
procedure TIdFTP.SetPassive(const AValue: Boolean);
begin
if (not AValue) and TryNATFastTrack then begin
raise EIdFTPPassiveMustBeTrueWithNATFT.Create(RSFTPFTPPassiveMustBeTrueWithNATFT);
end;
FPassive := AValue;
end;
procedure TIdFTP.SetTryNATFastTrack(const AValue: Boolean);
begin
FTryNATFastTrack := AValue;
if FTryNATFastTrack then begin
FPassive := True;
FUseExtensionDataPort := True;
end;
end;
procedure TIdFTP.DoTryNATFastTrack;
begin
if IsExtSupported('EPSV') then begin {do not localize}
if SendCmd('EPSV ALL') = 229 then begin {do not localize}
//Surge FTP treats EPSV ALL as if it were a standard EPSV
//We send ABOR in that case so it can close the data connection it created
SendCmd('ABOR'); {do not localize}
end;
FUsingNATFastTrack := True;
end;
end;
procedure TIdFTP.SetCmdOpt(const ACmd, AOptions: String);
begin
SendCmd('OPTS ' + ACmd + ' ' + AOptions, 200); {do not localize}
end;
procedure TIdFTP.ExtListDir(ADest: TStrings = nil; const ADirectory: string = '');
var
LDest: TMemoryStream;
LEncoding: IIdTextEncoding;
begin
// RLebeau 6/4/2009: According to RFC 3659 Section 7.2:
//
// The data connection opened for a MLSD response shall be a connection
// as if the "TYPE L 8", "MODE S", and "STRU F" commands had been given,
// whatever FTP transfer type, mode and structure had actually been set,
// and without causing those settings to be altered for future commands.
// That is, this transfer type shall be set for the duration of the data
// connection established for this command only. While the content of
// the data sent can be viewed as a series of lines, implementations
// should note that there is no maximum line length defined.
// Implementations should be prepared to deal with arbitrarily long
// lines.
LDest := TMemoryStream.Create;
try
InternalGet(Trim('MLSD ' + ADirectory), LDest); {do not localize}
FreeAndNil(FDirectoryListing);
FDirFormat := '';
DoOnRetrievedDir;
LDest.Position := 0;
// RLebeau: using IndyTextEncoding_8Bit here. TIdFTPListParseBase will
// decode UTF-8 sequences later on...
LEncoding := IndyTextEncoding_8Bit;
FListResult.Text := ReadStringFromStream(LDest, -1, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
LEncoding := nil;
TIdFTPListResult(FListResult).FDetails := True;
TIdFTPListResult(FListResult).FUsedMLS := True;
FDirFormat := MLST;
finally
FreeAndNil(LDest);
end;
if Assigned(ADest) then begin //APR: User can use ListResult and DirectoryListing
ADest.Assign(FListResult);
end;
end;
procedure TIdFTP.ExtListItem(ADest: TStrings; AFList : TIdFTPListItems; const AItem: string);
var
i : Integer;
begin
ADest.Clear;
SendCmd(Trim('MLST ' + AItem), 250, IndyTextEncoding_8Bit); {do not localize}
for i := 0 to LastCmdResult.Text.Count -1 do begin
if IndyPos(';', LastCmdResult.Text[i]) > 0 then begin
ADest.Add(LastCmdResult.Text[i]);
end;
end;
if Assigned(AFList) then begin
IdFTPListParseBase.ParseListing(ADest, AFList, 'MLST'); {do not localize}
end;
end;
procedure TIdFTP.ExtListItem(ADest: TStrings; const AItem: string);
begin
ExtListItem(ADest, nil, AItem);
end;
procedure TIdFTP.ExtListItem(AFList: TIdFTPListItems; const AItem: String);
var
LBuf : TStrings;
begin
LBuf := TStringList.Create;
try
ExtListItem(LBuf, AFList, AItem);
finally
FreeAndNil(LBuf);
end;
end;
function TIdFTP.IsExtSupported(const ACmd: String): Boolean;
var
i : Integer;
LBuf : String;
begin
Result := False;
for i := 0 to FCapabilities.Count -1 do begin
LBuf := TrimLeft(FCapabilities[i]);
if TextIsSame(Fetch(LBuf), ACmd) then begin
Result := True;
Exit;
end;
end;
end;
function TIdFTP.FileDate(const AFileName: String; const AsGMT: Boolean): TDateTime;
var
LBuf : String;
begin
//Do not use the FEAT list because some servers
//may support it even if FEAT isn't supported
if SendCmd('MDTM ' + AFileName) = 213 then begin {do not localize}
LBuf := LastCmdResult.Text[0];
LBuf := Trim(LBuf);
if AsGMT then begin
Result := FTPMLSToGMTDateTime(LBuf);
end else begin
Result := FTPMLSToLocalDateTime(LBuf);
end;
end else begin
Result := 0;
end;
end;
procedure TIdFTP.SiteToSiteUpload(const AToSite : TIdFTP; const ASourceFile : String;
const ADestFile : String = '');
{
SiteToSiteUpload
From: PASV To: PORT - ATargetUsesPasv = False
From: RETR To: STOR
SiteToSiteDownload
From: PORT To: PASV - ATargetUsesPasv = True
From: RETR To: STOR
}
begin
if ValidateInternalIsTLSFXP(Self, AToSite, True) then begin
InternalEncryptedTLSFXP(Self, AToSite, ASourceFile, ADestFile, True);
end else begin
InternalUnencryptedFXP(Self, AToSite, ASourceFile, ADestFile, True);
end;
end;
procedure TIdFTP.SiteToSiteDownload(const AFromSite: TIdFTP; const ASourceFile : String;
const ADestFile : String = '');
{
The only use of this function is to get the passive mode on the other connection.
Because not all hosts allow it. This way you get a second chance.
If uploading from host A doesn't work, try downloading from host B
}
begin
if ValidateInternalIsTLSFXP(AFromSite, Self, True) then begin
InternalEncryptedTLSFXP(AFromSite, Self, ASourceFile, ADestFile, False);
end else begin
InternalUnencryptedFXP(AFromSite, Self, ASourceFile, ADestFile, False);
end;
end;
procedure TIdFTP.ExtractFeatFacts(const ACmd: String; AResults: TStrings);
var
i : Integer;
LBuf, LFact : String;
begin
AResults.Clear;
for i := 0 to FCapabilities.Count -1 do begin
LBuf := FCapabilities[i];
if TextIsSame(Fetch(LBuf), ACmd) then begin
LBuf := Trim(LBuf);
while LBuf <> '' do begin
LFact := Trim(Fetch(LBuf, ';'));
if LFact <> '' then begin
AResults.Add(LFact);
end;
end;
Exit;
end;
end;
end;
procedure TIdFTP.SetLang(const ALangTag: String);
begin
if IsExtSupported('LANG') then begin {do not localize}
SendCMD('LANG ' + ALangTag, 200); {do not localize}
end;
end;
function TIdFTP.CRC(const AFIleName : String; const AStartPoint : Int64 = 0;
const AEndPoint : Int64 = 0) : Int64;
var
LCmd : String;
LCRC : String;
begin
Result := -1;
if IsExtSupported('XCRC') then begin {do not localize}
LCmd := 'XCRC "' + AFileName + '"'; {do not localize}
if AStartPoint <> 0 then begin
LCmd := LCmd + ' ' + IntToStr(AStartPoint);
if AEndPoint <> 0 then begin
LCmd := LCmd + ' ' + IntToStr(AEndPoint);
end;
end;
if SendCMD(LCMD) = 250 then begin
LCRC := Trim(LastCmdResult.Text.Text);
IdDelete(LCRC, 1, IndyPos(' ', LCRC)); // delete the response
Result := IndyStrToInt64('$' + LCRC, -1);
end;
end;
end;
procedure TIdFTP.CombineFiles(const ATargetFile: String; AFileParts: TStrings);
var
i : Integer;
LCmd: String;
begin
if IsExtSupported('COMB') and (AFileParts.Count > 0) then begin {do not localize}
LCmd := 'COMB "' + ATargetFile + '"'; {do not localize}
for i := 0 to AFileParts.Count -1 do begin
LCmd := LCmd + ' ' + AFileParts[i];
end;
SendCmd(LCmd, 250);
end;
end;
procedure TIdFTP.ParseFTPList;
begin
DoOnDirParseStart;
try
// Parse directory listing
if FListResult.Count > 0 then begin
if TIdFTPListResult(FListResult).UsedMLS then begin
FDirFormat := MLST;
// TODO: set the FListParserClass as well..
IdFTPListParseBase.ParseListing(FListResult, FDirectoryListing, MLST);
end else begin
CheckListParseCapa(FListResult, FDirectoryListing, FDirFormat,
FListParserClass, SystemDesc, TIdFTPListResult(FListResult).Details);
end;
end else begin
FDirFormat := '';
end;
finally
DoOnDirParseEnd;
end;
end;
function TIdFTP.GetSupportsTLS: Boolean;
begin
Result := (FindAuthCmd <> '');
end;
function TIdFTP.FindAuthCmd: String;
var
i : Integer;
LBuf : String;
LWord : String;
begin
Result := '';
for i := 0 to FCapabilities.Count -1 do begin
LBuf := TrimLeft(FCapabilities[i]);
if TextIsSame(Fetch(LBuf), 'AUTH') then begin {do not localize}
repeat
LWord := Trim(Fetch(LBuf, ';'));
if PosInStrArray(LWord, TLS_AUTH_NAMES, False) > -1 then begin
Result := 'AUTH ' + LWord; {do not localize}
Exit;
end;
until LBuf = '';
Break;
end;
end;
end;
procedure TIdFTP.DoCustomFTPProxy;
begin
if Assigned(FOnCustomFTPProxy) then begin
FOnCustomFTPProxy(Self);
end else begin
raise EIdFTPOnCustomFTPProxyRequired.Create(RSFTPOnCustomFTPProxyReq);
end;
end;
function TIdFTP.GetLoginPassword: String;
begin
Result := GetLoginPassword(LastCmdResult.Text.Text);
end;
function TIdFTP.GetLoginPassword(const APrompt: String): String;
begin
if TIdOTPCalculator.IsValidOTPString(APrompt) then begin
TIdOTPCalculator.GenerateSixWordKey(APrompt, FPassword, Result);
end else begin
Result := FPassword;
end;
end;
function TIdFTP.SetSSCNToOn : Boolean;
begin
Result := FUsingSFTP;
if not Result then begin
Exit;
end;
Result := (DataPortProtection = ftpdpsPrivate);
if not Result then begin
Exit;
end;
Result := not IsExtSupported(SCCN_FEAT);
if not Result then begin
Exit;
end;
if not FSSCNOn then begin
SendCmd(SSCN_ON, SSCN_OK_REPLY);
FSSCNOn := True;
end;
end;
procedure TIdFTP.ClearSSCN;
begin
if FSSCNOn then begin
SendCmd(SSCN_OFF, SSCN_OK_REPLY);
end;
end;
procedure TIdFTP.SetClientInfo(const AValue: TIdFTPClientIdentifier);
begin
FClientInfo.Assign(AValue);
end;
procedure TIdFTP.SetCompressor(AValue: TIdZLibCompressorBase);
var
// under ARC, convert a weak reference to a strong reference before working with it
LCompressor: TIdZLibCompressorBase;
begin
LCompressor := FCompressor;
if LCompressor <> AValue then begin
// under ARC, all weak references to a freed object get nil'ed automatically
{$IFNDEF USE_OBJECT_ARC}
if Assigned(LCompressor) then begin
LCompressor.RemoveFreeNotification(Self);
end;
{$ENDIF}
FCompressor := AValue;
if Assigned(AValue) then begin
{$IFNDEF USE_OBJECT_ARC}
AValue.FreeNotification(Self);
{$ENDIF}
end
else if Connected then begin
TransferMode(dmStream);
end;
end;
end;
procedure TIdFTP.GetInternalResponse(AEncoding: IIdTextEncoding = nil);
var
LLine: string;
LResponse: TStringList;
LReplyCode: string;
begin
CheckConnected;
LResponse := TStringList.Create;
try
// Some servers with bugs send blank lines before reply. Dont remember
// which ones, but I do remember we changed this for a reason
//
// RLebeau 9/14/06: this can happen in between lines of the reply as well
// RLebeau 3/9/09: according to RFC 959, when reading a multi-line reply,
// we are supposed to look at the first line's reply code and then keep
// reading until that specific reply code is encountered again, and
// everything in between is the text. So, do not just look for arbitrary
// 3-digit values on each line, but instead look for the specific reply
// code...
LLine := IOHandler.ReadLnWait(MaxInt, AEncoding);
LResponse.Add(LLine);
if CharEquals(LLine, 4, '-') then begin
LReplyCode := Copy(LLine, 1, 3);
repeat
LLine := IOHandler.ReadLnWait(MaxInt, AEncoding);
LResponse.Add(LLine);
until TIdReplyFTP(FLastCmdResult).IsEndReply(LReplyCode, LLine);
end;
//Note that FormattedReply uses an assign in it's property set method.
FLastCmdResult.FormattedReply := LResponse;
finally
FreeAndNil(LResponse);
end;
end;
function TIdFTP.CheckResponse(const AResponse: Int16;
const AAllowedResponses: array of Int16): Int16;
var
i: Integer;
begin
// any FTP command can return a 421 reply if the server is going to shut
// down the command connection. This way, we can close the connection
// immediately instead of waiting for a future action that would raise
// an EIdConnClosedGracefully exception instead...
if AResponse = 421 then
begin
// check if the caller explicitally wants to handle 421 replies...
if High(AAllowedResponses) > -1 then begin
for i := Low(AAllowedResponses) to High(AAllowedResponses) do begin
if AResponse = AAllowedResponses[i] then begin
Result := AResponse;
Exit;
end;
end;
end;
Disconnect(False);
if IOHandler <> nil then begin
IOHandler.InputBuffer.Clear;
end;
RaiseExceptionForLastCmdResult;
end;
Result := inherited CheckResponse(AResponse, AAllowedResponses);
end;
function TIdFTP.GetReplyClass: TIdReplyClass;
begin
Result := TIdReplyFTP;
end;
procedure TIdFTP.SetIPVersion(const AValue: TIdIPVersion);
begin
if AValue <> FIPVersion then begin
inherited SetIPVersion(AValue);
if IPVersion = Id_IPv6 then begin
UseExtensionDataPort := True;
end;
end;
end;
class function TIdFTP.InternalEncryptedTLSFXP(AFromSite, AToSite: TIdFTP;
const ASourceFile, ADestFile: String; const ATargetUsesPasv : Boolean): Boolean;
{
SiteToSiteUpload
From: PASV To: PORT - ATargetUsesPasv = False
From: RETR To: STOR
SiteToSiteDownload
From: PORT To: PASV - ATargetUsesPasv = True
From: RETR To: STOR
To do FXP transfers with TLS FTP, you have to have one computer do the
TLS handshake as a client (ssl_connect). Thus, one of the following conditions must be meet.
1) SSCN must be supported on one of the FTP servers
or
2) If IPv4 is used, the computer receiving a "PASV" command must support
CPSV. CPSV will NOT work with IPv6.
IMAO, when doing FXP transfers, you should use SSCN whenever possible as
SSCN will support IPv6 and SSCN may be in wider use than CPSV. CPSV should
only be used as a fallback if SSCN isn't supported by both servers and IPv4
is being used.
}
var
LIP : String;
LPort : TIdPort;
begin
Result := True;
if AFromSite.SetSSCNToOn then begin
AToSite.ClearSSCN;
end
else if AToSite.SetSSCNToOn then begin
AFromSite.ClearSSCN;
end
else if AToSite.IPVersion = Id_IPv4 then begin
if ATargetUsesPasv then begin
AToSite.SendCPassive(LIP, LPort);
AFromSite.SendPort(LIP, LPort);
end else begin
AFromSite.SendCPassive(LIP, LPort);
AToSite.SendPort(LIP, LPort);
end;
end;
FXPSendFile(AFromSite, AToSite, ASourceFile, ADestFile);
end;
class function TIdFTP.InternalUnencryptedFXP(AFromSite, AToSite: TIdFTP;
const ASourceFile, ADestFile: String; const ATargetUsesPasv : Boolean): Boolean;
{
SiteToSiteUpload
From: PASV To: PORT - ATargetUsesPasv = False
From: RETR To: STOR
SiteToSiteDownload
From: PORT To: PASV - ATargetUsesPasv = True
From: RETR To: STOR
}
begin
FXPSetTransferPorts(AFromSite, AToSite, ATargetUsesPasv);
FXPSendFile(AFromSite, AToSite, ASourceFile, ADestFile);
Result := True;
end;
class function TIdFTP.ValidateInternalIsTLSFXP(AFromSite, AToSite: TIdFTP;
const ATargetUsesPasv : Boolean): Boolean;
{
SiteToSiteUpload
From: PASV To: PORT - ATargetUsesPasv = False
From: RETR To: STOR
SiteToSiteDownload
From: PORT To: PASV - ATargetUsesPasv = True
From: RETR To: STOR
This will raise an exception if FXP can not be done. Result = True for encrypted
or False for unencrypted.
Note:
The following is required:
SiteToSiteUpload
Source must do P
}
begin
if ATargetUsesPasv then begin
if AToSite.UsingNATFastTrack then begin
raise EIdFTPSToSNATFastTrack.Create(RSFTPNoSToSWithNATFastTrack);
end;
end else begin
if AFromSite.UsingNATFastTrack then begin
raise EIdFTPSToSNATFastTrack.Create(RSFTPNoSToSWithNATFastTrack);
end;
end;
if AFromSite.IPVersion <> AToSite.IPVersion then begin
raise EIdFTPStoSIPProtoMustBeSame.Create(RSFTPSToSProtosMustBeSame);
end;
if AFromSite.CurrentTransferMode <> AToSite.CurrentTransferMode then begin
raise EIdFTPSToSTransModesMustBeSame.Create(RSFTPSToSTransferModesMusbtSame);
end;
if AFromSite.FUsingSFTP <> AToSite.FUsingSFTP then begin
raise EIdFTPSToSNoDataProtection.Create(RSFTPSToSNoDataProtection);
end;
Result := AFromSite.FUsingSFTP and AToSite.FUsingSFTP;
if Result then begin
if not (AFromSite.IsExtSupported('SSCN') or AToSite.IsExtSupported('SSCN')) then begin {do not localize}
//Second chance fallback, is CPSV supported on the server where PASV would
// be sent
if AToSite.IPVersion = Id_IPv4 then begin
if ATargetUsesPasv then begin
if not AToSite.IsExtSupported('CPSV') then begin {do not localize}
raise EIdFTPSToSNATFastTrack.Create(RSFTPSToSSSCNNotSupported);
end;
end else begin
if not AFromSite.IsExtSupported('CPSV') then begin {do not localize}
raise EIdFTPSToSNATFastTrack.Create(RSFTPSToSSSCNNotSupported);
end;
end;
end;
end;
end;
end;
class procedure TIdFTP.FXPSendFile(AFromSite, AToSite: TIdFTP; const ASourceFile, ADestFile: String);
var
LDestFile : String;
begin
LDestFile := ADestFile;
if LDestFile = '' then begin
LDestFile := ASourceFile;
end;
AToSite.SendCmd('STOR ' + LDestFile, [110, 125, 150]); {do not localize}
try
AFromSite.SendCmd('RETR ' + ASourceFile, [110, 125, 150]); {do not localize}
except
AToSite.Abort;
raise;
end;
AToSite.GetInternalResponse;
AFromSite.GetInternalResponse;
AToSite.CheckResponse(AToSite.LastCmdResult.NumericCode, [225, 226, 250]);
AFromSite.CheckResponse(AFromSite.LastCmdResult.NumericCode, [225, 226, 250]);
end;
class procedure TIdFTP.FXPSetTransferPorts(AFromSite, AToSite: TIdFTP; const ATargetUsesPasv: Boolean);
var
LIP : String;
LPort : TIdPort;
{
{
SiteToSiteUpload
From: PASV To: PORT - ATargetUsesPasv = False
From: RETR To: STOR
SiteToSiteDownload
From: PORT To: PASV - ATargetUsesPasv = True
From: RETR To: STOR
}
begin
if ATargetUsesPasv then begin
if AToSite.UsingExtDataPort then begin
AToSite.SendEPassive(LIP, LPort);
end else begin
AToSite.SendPassive(LIP, LPort);
end;
if AFromSite.UsingExtDataPort then begin
AFromSite.SendEPort(LIP, LPort, AToSite.IPVersion);
end else begin
AFromSite.SendPort(LIP, LPort);
end;
end else begin
if AFromSite.UsingExtDataPort then begin
AFromSite.SendEPassive(LIP, LPort);
end else begin
AFromSite.SendPassive(LIP, LPort);
end;
if AToSite.UsingExtDataPort then begin
AToSite.SendEPort(LIP, LPort, AFromSite.IPVersion);
end else begin
AToSite.SendPort(LIP, LPort);
end;
end;
end;
{ TIdFTPClientIdentifier }
procedure TIdFTPClientIdentifier.Assign(Source: TPersistent);
var
LSource: TIdFTPClientIdentifier;
begin
if Source is TIdFTPClientIdentifier then begin
LSource := TIdFTPClientIdentifier(Source);
ClientName := LSource.ClientName;
ClientVersion := LSource.ClientVersion;
PlatformDescription := LSource.PlatformDescription;
end else begin
inherited Assign(Source);
end;
end;
//assume syntax such as this:
//214 Syntax: CLNT <sp> <client-name> <sp> <client-version> [<sp> <optional platform info>] (Set client name)
function TIdFTPClientIdentifier.GetClntOutput: String;
begin
if FClientName <> '' then begin
Result := FClientName;
if FClientVersion <> '' then begin
Result := Result + ' ' + FClientVersion;
if FPlatformDescription <> '' then begin
Result := Result + ' ' + FPlatformDescription;
end;
end;
end else begin
Result := '';
end;
end;
procedure TIdFTPClientIdentifier.SetClientName(const AValue: String);
begin
FClientName := Trim(AValue);
// Don't call Fetch; it prevents multi-word client names
end;
procedure TIdFTPClientIdentifier.SetClientVersion(const AValue: String);
begin
FClientVersion := Trim(AValue);
end;
procedure TIdFTPClientIdentifier.SetPlatformDescription(const AValue: String);
begin
FPlatformDescription := AValue;
end;
{Note about SetTime procedures:
The first syntax is one used by current Serv-U versions and servers that report "MDTM YYYYMMDDHHMMSS[+-TZ];filename " in their FEAT replies is:
1) MDTM [Time in GMT format] Filename
some Bullete Proof FTPD versions, Indy's FTP Server component, and servers reporting "MDTM YYYYMMDDHHMMSS[+-TZ] filename" in their FEAT replies uses an older Syntax which is:
2) MDTM yyyymmddhhmmss[+-minutes from Universal Time] Filename
and then there is the classic
3) MDTM [local timestamp] Filename
So for example, if I was a file dated Jan 3, 5:00:00 pm from my computer in the Eastern Standard Time (-5 hours from Universal Time), the 3 syntaxes
Indy would use are:
Syntax 1:
1) MDTM 0103220000 MyFile.exe <20>(notice the 22 hour)
Syntax 2:
2) MDTM 0103170000-300 MyFile.exe (notice the 17 hour and the -300 offset)
Syntax 3;
3) MDTM 0103170000 MyFile.exe (notice the 17 hour)
Note from:
http://www.ftpvoyager.com/releasenotes10x.asp
====
Added support for RFC change and the MDTM. MDTM requires sending the server
GMT (UTC) instead of a "fixed" date and time. FTP Voyager supports this with
Serv-U automatically by checking the Serv-U version number and by checking the
response to the FEAT command for MDTM. Servers returning "MDTM" or
"MDTM YYYYMMDDHHMMSS[+-TZ] filename" will use the old method. Servers
returning "MDTM YYYYMMDDHHMMSS" only will use the new method where the date a
and time is GMT (UTC).
===
}
procedure TIdFTP.SetModTime(const AFileName: String; const ALocalTime: TDateTime);
var
LCmd: String;
begin
//use MFMT instead of MDTM because that always takes the time as Universal
//time (the most accurate).
if IsExtSupported('MFMT') then begin {do not localize}
LCmd := 'MFMT ' + FTPLocalDateTimeToMLS(ALocalTime, False) + ' ' + AFileName; {do not localize}
end
//Syntax 1 - MDTM [Time in GMT format] Filename
else if (IndexOfFeatLine('MDTM YYYYMMDDHHMMSS[+-TZ];filename') > 0) or IsIIS then begin {do not localize}
//we use the new method
LCmd := 'MDTM ' + FTPLocalDateTimeToMLS(ALocalTime, False) + ' ' + AFileName; {do not localize}
end
//Syntax 2 - MDTM yyyymmddhhmmss[+-minutes from Universal Time] Filename
//use old method for old versions of Serv-U and BPFTP Server
else if (IndexOfFeatLine('MDTM YYYYMMDDHHMMSS[+-TZ] filename') > 0) or IsOldServU or IsBPFTP then begin {do not localize}
LCmd := 'MDTM '+ FTPDateTimeToMDTMD(ALocalTime, False, True) + ' ' + AFileName; {do not localize}
end
//syntax 3 - MDTM [local timestamp] Filename
else if FTZInfo.FGMTOffsetAvailable then begin
//send it relative to the server's time-zone
LCmd := 'MDTM '+ FTPDateTimeToMDTMD(ALocalTime - OffSetFromUTC + FTZInfo.FGMTOffset, False, False) + ' ' + AFileName; {do not localize}
end
else begin
LCmd := 'MDTM '+ FTPDateTimeToMDTMD(ALocalTime, False, False) + ' ' + AFileName; {do not localize}
end;
// When using MDTM, Titan FTP 5 returns 200 and vsFTPd returns 213
SendCmd(LCmd, [200, 213, 253]);
end;
{
Note from:
http://www.ftpvoyager.com/releasenotes10x.asp
====
Added support for RFC change and the MDTM. MDTM requires sending the server
GMT (UTC) instead of a "fixed" date and time. FTP Voyager supports this with
Serv-U automatically by checking the Serv-U version number and by checking the
response to the FEAT command for MDTM. Servers returning "MDTM" or
"MDTM YYYYMMDDHHMMSS[+-TZ] filename" will use the old method. Servers
returning "MDTM YYYYMMDDHHMMSS" only will use the new method where the date a
and time is GMT (UTC).
===
}
procedure TIdFTP.SetModTimeGMT(const AFileName : String; const AGMTTime: TDateTime);
var
LCmd: String;
begin
//use MFMT instead of MDTM because that always takes the time as Universal
//time (the most accurate).
if IsExtSupported('MFMT') then begin {do not localize}
LCmd := 'MFMT ' + FTPGMTDateTimeToMLS(AGMTTime) + ' ' + AFileName; {do not localize}
end
//Syntax 1 - MDTM [Time in GMT format] Filename
else if (IndexOfFeatLine('MDTM YYYYMMDDHHMMSS[+-TZ];filename') > 0) or IsIIS then begin {do not localize}
//we use the new method
LCmd := 'MDTM ' + FTPGMTDateTimeToMLS(AGMTTime, False) + ' ' + AFileName; {do not localize}
end
//Syntax 2 - MDTM yyyymmddhhmmss[+-minutes from Universal Time] Filename
//use old method for old versions of Serv-U and BPFTP Server
else if (IndexOfFeatLine('MDTM YYYYMMDDHHMMSS[+-TZ] filename') > 0) or IsOldServU or IsBPFTP then begin {do not localize}
LCmd := 'MDTM '+ FTPDateTimeToMDTMD(AGMTTime + OffSetFromUTC, False, True) + ' ' + AFileName; {do not localize}
end
//syntax 3 - MDTM [local timestamp] Filename
else if FTZInfo.FGMTOffsetAvailable then begin
//send it relative to the server's time-zone
LCmd := 'MDTM '+ FTPDateTimeToMDTMD(AGMTTime + FTZInfo.FGMTOffset, False, False) + ' ' + AFileName; {do not localize}
end
else begin
LCmd := 'MDTM '+ FTPDateTimeToMDTMD(AGMTTime + OffSetFromUTC, False, False) + ' ' + AFileName; {do not localize}
end;
// When using MDTM, Titan FTP 5 returns 200 and vsFTPd returns 213
SendCmd(LCmd, [200, 213, 253]);
end;
{Improvement from Tobias Giesen http://www.superflexible.com
His notation is below:
"here's a fix for TIdFTP.IndexOfFeatLine. It does not work the
way it is used in TIdFTP.SetModTime, because it only
compares the first word of the FeatLine." }
function TIdFTP.IndexOfFeatLine(const AFeatLine: String): Integer;
var
LBuf : String;
LNoSpaces: Boolean;
begin
LNoSpaces := IndyPos(' ', AFeatLine) = 0;
for Result := 0 to FCapabilities.Count -1 do begin
LBuf := TrimLeft(FCapabilities[Result]);
// RLebeau: why Fetch() if no spaces are present?
if LNoSpaces then begin
LBuf := Fetch(LBuf);
end;
if TextIsSame(AFeatLine, LBuf) then begin
Exit;
end;
end;
Result := -1;
end;
{ TIdFTPTZInfo }
procedure TIdFTPTZInfo.Assign(Source: TPersistent);
var
LSource: TIdFTPTZInfo;
begin
if Source is TIdFTPTZInfo then begin
LSource := TIdFTPTZInfo(Source);
FGMTOffset := LSource.GMTOffset;
FGMTOffsetAvailable := LSource.GMTOffsetAvailable;
end else begin
inherited Assign(Source);
end;
end;
function TIdFTP.IsSiteZONESupported: Boolean;
var
LFacts : TStrings;
i : Integer;
begin
Result := False;
if IsServerMDTZAndListTForm then begin
Result := True;
Exit;
end;
LFacts := TStringList.Create;
try
ExtractFeatFacts('SITE', LFacts);
for i := 0 to LFacts.Count-1 do begin
if TextIsSame(LFacts[i], 'ZONE') then begin {do not localize}
Result := True;
Exit;
end;
end;
finally
FreeAndNil(LFacts);
end;
end;
procedure TIdFTP.SetTZInfo(const Value: TIdFTPTZInfo);
begin
FTZInfo.Assign(Value);
end;
function TIdFTP.IsOldServU: Boolean;
begin
Result := TextStartsWith(FServerDesc, 'Serv-U '); {do not localize}
end;
function TIdFTP.IsBPFTP : Boolean;
begin
Result := TextStartsWith(FServerDesc, 'BPFTP Server '); {do not localize}
end;
function TIdFTP.IsTitan : Boolean;
begin
Result := TextStartsWith(FServerDesc, 'TitanFTP server ') or {do not localize}
TextStartsWith(FServerDesc, 'Titan FTP Server '); {do not localize}
end;
function TIdFTP.IsWSFTP : Boolean;
begin
Result := IndyPos('WS_FTP Server', FServerDesc) > 0; {do not localize}
end;
function TIdFTP.IsIIS: Boolean;
begin
Result := TextStartsWith(FServerDesc, 'Microsoft FTP Service'); {do not localize}
end;
function TIdFTP.IsServerMDTZAndListTForm: Boolean;
begin
Result := IsOldServU or IsBPFTP or IsTitan;
end;
procedure TIdFTP.Notification(AComponent: TComponent; Operation: TOperation);
begin
if (Operation = opRemove) and (AComponent = FCompressor) then begin
SetCompressor(nil);
end;
inherited Notification(AComponent, Operation);
end;
procedure TIdFTP.SendPret(const ACommand: String);
begin
if IsExtSupported('PRET') then begin {do not localize}
//note that we don't check for success or failure here
//as some servers might fail and then succede with the transfer.
//Pret might not work for some commands.
SendCmd('PRET ' + ACommand); {do not localize}
end;
end;
procedure TIdFTP.List;
begin
List(nil);
end;
procedure TIdFTP.List(const ASpecifier: string; ADetails: Boolean);
begin
List(nil, ASpecifier, ADetails);
end;
procedure TIdFTP.DoOnBannerAfterLogin(AText: TStrings);
begin
if Assigned(OnBannerAfterLogin) then begin
OnBannerAfterLogin(Self, AText.Text);
end;
end;
procedure TIdFTP.DoOnBannerBeforeLogin(AText: TStrings);
begin
if Assigned(OnBannerBeforeLogin) then begin
OnBannerBeforeLogin(Self, AText.Text);
end;
end;
procedure TIdFTP.DoOnBannerWarning(AText: TStrings);
begin
if Assigned(OnBannerWarning) then begin
OnBannerWarning(Self, AText.Text);
end;
end;
procedure TIdFTP.SetDataPortProtection(AValue: TIdFTPDataPortSecurity);
begin
if IsLoading then begin
FDataPortProtection := AValue;
Exit;
end;
if FDataPortProtection <> AValue then begin
if FUseTLS = utNoTLSSupport then begin
raise EIdFTPNoDataPortProtectionWOEncryption.Create(RSFTPNoDataPortProtectionWOEncryption);
end;
if FUsingCCC then begin
raise EIdFTPNoDataPortProtectionAfterCCC.Create(RSFTPNoDataPortProtectionAfterCCC);
end;
FDataPortProtection := AValue;
end;
end;
procedure TIdFTP.SetAUTHCmd(const AValue : TAuthCmd);
begin
if IsLoading then begin
FAUTHCmd := AValue;
Exit;
end;
if FAUTHCmd <> AValue then begin
if FUseTLS = utNoTLSSupport then begin
raise EIdFTPNoAUTHWOSSL.Create(RSFTPNoAUTHWOSSL);
end;
if FUsingSFTP then begin
raise EIdFTPCanNotSetAUTHCon.Create(RSFTPNoAUTHCon);
end;
FAUTHCmd := AValue;
end;
end;
procedure TIdFTP.SetDefStringEncoding(AValue: IIdTextEncoding);
begin
FDefStringEncoding := AValue;
if IOHandler <> nil then begin
IOHandler.DefStringEncoding := FDefStringEncoding;
end;
end;
procedure TIdFTP.SetUseTLS(AValue: TIdUseTLS);
begin
inherited SetUseTLS(AValue);
if IsLoading then begin
Exit;
end;
if AValue = utNoTLSSupport then begin
FDataPortProtection := Id_TIdFTP_DataPortProtection;
FUseCCC := DEF_Id_FTP_UseCCC;
FAUTHCmd := DEF_Id_FTP_AUTH_CMD;
end;
end;
procedure TIdFTP.SetUseCCC(const AValue: Boolean);
begin
if (not IsLoading) and (FUseTLS = utNoTLSSupport) then begin
raise EIdFTPNoCCCWOEncryption.Create(RSFTPNoCCCWOEncryption);
end;
FUseCCC := AValue;
end;
procedure TIdFTP.DoOnRetrievedDir;
begin
if Assigned(OnRetrievedDir) then begin
OnRetrievedDir(Self);
end;
end;
procedure TIdFTP.DoOnDirParseEnd;
begin
if Assigned(FOnDirParseEnd) then begin
FOnDirParseEnd(Self);
end;
end;
procedure TIdFTP.DoOnDirParseStart;
begin
if Assigned(FOnDirParseStart) then begin
FOnDirParseStart(Self);
end;
end;
//we do this to match some WS-FTP Pro firescripts I saw
function TIdFTP.IsAccountNeeded: Boolean;
begin
Result := LastCmdResult.NumericCode = 332;
if not Result then begin
if IndyPos('ACCOUNT', LastCmdResult.Text.Text) > 0 then begin {do not localize}
Result := FAccount <> '';
end;
end;
end;
//we can use one of three commands for verifying a file or stream
function TIdFTP.GetSupportsVerification: Boolean;
begin
Result := Connected;
if Result then begin
Result := TIdHashSHA512.IsAvailable and IsExtSupported('XSHA512');
if not Result then begin
Result := TIdHashSHA256.IsAvailable and IsExtSupported('XSHA256');
end;
if not Result then begin
Result := IsExtSupported('XSHA1') or
(IsExtSupported('XMD5') and (not GetFIPSMode)) or
IsExtSupported('XCRC');
end;
end;
end;
function TIdFTP.VerifyFile(const ALocalFile, ARemoteFile: String; const AStartPoint, AByteCount: TIdStreamSize): Boolean;
var
LLocalStream: TStream;
LRemoteFileName : String;
begin
LRemoteFileName := ARemoteFile;
if LRemoteFileName = '' then begin
LRemoteFileName := ExtractFileName(ALocalFile);
end;
LLocalStream := TIdReadFileExclusiveStream.Create(ALocalFile);
try
Result := VerifyFile(LLocalStream, LRemoteFileName, AStartPoint, AByteCount);
finally
FreeAndNil(LLocalStream);
end;
end;
{
This procedure can use three possible commands to verify file integriety and the
syntax does very amoung these. The commands are:
XSHA1 - get SHA1 checksum for a file or file part
XMD5 - get MD5 checksum for a file or file part
XCRC - get CRC32 checksum
The command preference is from first to last (going from longest length to shortest).
}
function TIdFTP.VerifyFile(ALocalFile: TStream; const ARemoteFile: String;
const AStartPoint, AByteCount: TIdStreamSize): Boolean;
var
LRemoteCRC : String;
LLocalCRC : String;
LCmd : String;
LRemoteFile: String;
LStartPoint : TIdStreamSize;
LByteCount : TIdStreamSize; //used instead of AByteCount so we don't exceed the file size
LHashClass: TIdHashClass;
LHash: TIdHash;
begin
LLocalCRC := '';
LRemoteCRC := '';
if AStartPoint > -1 then begin
ALocalFile.Position := AStartPoint;
end;
LStartPoint := ALocalFile.Position;
LByteCount := ALocalFile.Size - LStartPoint;
if (LByteCount > AByteCount) and (AByteCount > 0) then begin
LByteCount := AByteCount;
end;
//just in case the server doesn't support file names in quotes.
if IndyPos(' ', ARemoteFile) > 0 then begin
LRemoteFile := '"' + ARemoteFile + '"';
end else begin
LRemoteFile := ARemoteFile;
end;
if TIdHashSHA512.IsAvailable and IsExtSupported('XSHA512') then begin
//XSHA256 <sp> pathname [<sp> startposition <sp> endposition]
LCmd := 'XSHA512 ' + LRemoteFile;
if AByteCount > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint) + ' ' + IntToStr(LByteCount);
end
else if AStartPoint > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint);
end;
LHashClass := TIdHashSHA512;
end
else if TIdHashSHA256.IsAvailable and IsExtSupported('XSHA256') then begin
//XSHA256 <sp> pathname [<sp> startposition <sp> endposition]
LCmd := 'XSHA256 ' + LRemoteFile;
if AByteCount > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint) + ' ' + IntToStr(LByteCount);
end
else if AStartPoint > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint);
end;
LHashClass := TIdHashSHA256;
end
else if IsExtSupported('XSHA1') then begin
//XMD5 "filename" startpos endpos
//I think there's two syntaxes to this:
//
//Raiden Syntax if FEAT line contains " XMD5 filename;start;end"
//
//or what's used by some other servers if "FEAT line contains XMD5"
//
//XCRC "filename" [startpos] [number of bytes to calc]
if IndexOfFeatLine('XSHA1 filename;start;end') > -1 then begin
LCmd := 'XSHA1 ' + LRemoteFile + ' ' + IntToStr(LStartPoint) + ' ' + IntToStr(LStartPoint + LByteCount-1);
end else
begin
//BlackMoon FTP Server uses this one.
LCmd := 'XSHA1 ' + LRemoteFile;
if AByteCount > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint) + ' ' + IntToStr(LByteCount);
end
else if AStartPoint > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint);
end;
end;
LHashClass := TIdHashSHA1;
end
else if IsExtSupported('XMD5') and (not GetFIPSMode) then begin
//XMD5 "filename" startpos endpos
//I think there's two syntaxes to this:
//
//Raiden Syntax if FEAT line contains " XMD5 filename;start;end"
//
//or what's used by some other servers if "FEAT line contains XMD5"
//
//XCRC "filename" [startpos] [number of bytes to calc]
if IndexOfFeatLine('XMD5 filename;start;end') > -1 then begin
LCmd := 'XMD5 ' + LRemoteFile + ' ' + IntToStr(LStartPoint) + ' ' + IntToStr(LStartPoint + LByteCount-1);
end else
begin
//BlackMoon FTP Server uses this one.
LCmd := 'XMD5 ' + LRemoteFile;
if AByteCount > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint) + ' ' + IntToStr(LByteCount);
end
else if AStartPoint > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint);
end;
end;
LHashClass := TIdHashMessageDigest5;
end else
begin
LCmd := 'XCRC ' + LRemoteFile;
if AByteCount > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint) + ' ' + IntToStr(LByteCount);
end
else if AStartPoint > 0 then begin
LCmd := LCmd + ' ' + IntToStr(LStartPoint);
end;
LHashClass := TIdHashCRC32;
end;
LHash := LHashClass.Create;
try
LLocalCRC := LHash.HashStreamAsHex(ALocalFile, LStartPoint, LByteCount);
finally
LHash.Free;
end;
if SendCmd(LCmd) = 250 then begin
LRemoteCRC := Trim(LastCmdResult.Text.Text);
IdDelete(LRemoteCRC, 1, IndyPos(' ', LRemoteCRC)); // delete the response
Result := TextIsSame(LLocalCRC, LRemoteCRC);
end else begin
Result := False;
end;
end;
end.