Download Reference Manual
The Developer's Library for D
About Wiki Forums Source Search Contact

Changeset 3395

Show
Ignore:
Timestamp:
03/24/08 17:55:03 (9 months ago)
Author:
lmartin92
Message:

Updated FtpClient?.d to have all(may of missed a few)non-data commands supported with the newer telnet.d that now relies on SocketConduits? rather than Sockets. Reading/writing/creating/appending/(file operations) are not implemented. Mkdir/del/rm work. None has been tested. Next I will work on data commands

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/lmartin/ftp/tango/net/ftp/FtpClient.d

    <
    r3368 r3395  
    1 /******************************************************************************* 
    2  
    3         copyright:      Copyright (c) 2006 UWB. All rights reserved 
    4  
    5         license:        BSD style: $(LICENSE) 
    6  
    7         version:        Initial release: June 2006 
    8  
    9         author:         UWB 
    10  
    11         modifications are: 
    12  
    13             copyrighted as      Copyright (c) 2008 (c) Lester L. Martin II. All rights reserved 
    14  
    15             licensed    as      BSD style: $(LICENSE) 
    16  
    17             versioned   as      Initial modified release: _________ 2008 
    18  
    19             authored    by      Lester L. Martin II 
    20  
    21 *******************************************************************************/ 
    22  
     1/** 
     2 * Author: Lester L. Martin II 
     3 * Copyright (c) Lester L. Martin II 
     4 * Based upon prior FtpClient.d (some code is borrowed) 
     5 * License: $(BSD Liscense) 
     6 */ 
    237module tango.net.ftp.FtpClient; 
    248 
    25 private import  tango.net.Socket, 
    26                 tango.net.SocketConduit; 
    27  
    28 private import  tango.net.ftp.Telnet; 
    29  
    30 private import  tango.time.Clock; 
    31  
    32 private import  tango.io.Conduit, 
    33                 tango.io.GrowBuffer, 
    34                 tango.io.FileConduit; 
    35  
    36 private import  tango.time.chrono.Gregorian; 
    37  
    38 private import  Text = tango.text.Util; 
    39  
    40 private import  Ascii = tango.text.Ascii; 
    41  
    42 private import  Regex = tango.text.Regex; 
    43  
    44 private import  Integer = tango.text.convert.Integer; 
    45  
    46 private import  Timestamp = tango.text.convert.TimeStamp; 
    47  
    48 private import tango.text.Util; 
    49  
     9private { 
     10    import tango.net.ftp.Telnet; 
     11    import tango.text.Util; 
     12    import Integer = tango.text.convert.Integer; 
     13    import tango.net.Socket; 
     14    import tango.net.SocketConduit; 
     15    private import tango.time.Clock; 
     16
    5017 
    5118/// An FTP progress delegate. 
     
    6027 
    6128/// The format of data transfer. 
    62 enum FtpFormat 
    63 
    64     /// Indicates ASCII NON PRINT format (line ending conversion to CRLF.) 
    65     ascii, 
    66     /// Indicates IMAGE format (8 bit binary octets.) 
    67     image, 
     29enum FtpFormat { 
     30    /// Indicates ASCII NON PRINT format (line ending conversion to CRLF.) 
     31    ascii, 
     32    /// Indicates IMAGE format (8 bit binary octets.) 
     33    image, 
    6834} 
    6935 
    7036///A FtpAddress structure that contains all that is needed to access a FTPConnection; Contributed by Bobef 
    71 struct FtpAddress 
    72 
    73     static FtpAddress* opCall(char[] str) 
    74     { 
    75         if(str.length==0) return null; 
    76         try 
    77         { 
    78             auto ret=new FtpAddress; 
     37struct FtpAddress { 
     38    static FtpAddress* opCall(char[] str) { 
     39        if(str.length == 0) 
     40            return null; 
     41        try { 
     42            auto ret = new FtpAddress; 
    7943            //remove ftp:// 
    80             auto i=locatePattern(str,"ftp://"); 
    81             if(i==0) str=str[6..$]; 
     44            auto i = locatePattern(str, "ftp://"); 
     45            if(i == 0) 
     46                str = str[6 .. $]; 
    8247 
    8348            //check for username and/or password user[:pass]@ 
    84             i=locatePrior(str,'@'); 
    85             if(i!=str.length) 
    86             { 
    87                 char[] up=str[0..i]; 
    88                 str=str[i+1..$]; 
    89                 i=locate(up,':'); 
    90                 if(i!=up.length) 
    91                 { 
    92                     ret.user=up[0..i]; 
    93                     ret.pass=up[i+1..$]; 
    94                 } 
    95                 else ret.user=up; 
     49            i = locatePrior(str, '@'); 
     50            if(i != str.length) { 
     51                char[] up = str[0 .. i]; 
     52                str = str[i + 1 .. $]; 
     53                i = locate(up, ':'); 
     54                if(i != up.length) { 
     55                    ret.user = up[0 .. i]; 
     56                    ret.pass = up[i + 1 .. $]; 
     57                } else 
     58                    ret.user = up; 
    9659            } 
    9760 
    9861            //check for port 
    99             i=locatePrior(str,':'); 
    100             if(i!=str.length) 
    101             { 
    102                 ret.port=cast(uint)Integer.toLong(str[i+1..$]); 
    103                 str=str[0..i]; 
     62            i = locatePrior(str, ':'); 
     63            if(i != str.length) { 
     64                ret.port = cast(uint) Integer.toLong(str[i + 1 .. $]); 
     65                str = str[0 .. i]; 
    10466            } 
    10567 
    10668            //check any directories after the adress 
    107             i=locate(str,'/'); 
    108             if(i!=str.length) ret.directory=str[i+1..$]; 
     69            i = locate(str, '/'); 
     70            if(i != str.length) 
     71                ret.directory = str[i + 1 .. $]; 
    10972 
    11073            //the rest should be the address 
    111             ret.address=str[0..i]; 
    112             if(ret.address.length==0) return null; 
     74            ret.address = str[0 .. i]; 
     75            if(ret.address.length == 0) 
     76                return null; 
    11377 
    11478            return ret; 
    11579 
    116         }catch(Object o) {return null;} 
     80        } catch(Object o) { 
     81            return null; 
     82        } 
    11783    } 
    11884 
     
    12187    char[] user = "anonymous"; 
    12288    char[] pass = "anonymous@anonymous"; 
    123     uint port=21; 
     89    uint port = 21; 
    12490} 
    12591 
    12692/// A server response, consisting of a code and a potentially multi-line message. 
    127 struct FtpResponse 
    128 
    129     /// The response code. 
    130     /// 
    131     /// The digits in the response code can be used to determine status 
    132     /// programatically. 
    133     /// 
    134     /// First Digit (status): 
    135     ///    1xx =             a positive, but preliminary, reply 
    136     ///    2xx =             a positive reply indicating completion 
    137     ///    3xx =             a positive reply indicating incomplete status 
    138     ///    4xx =             a temporary negative reply 
    139     ///    5xx =             a permanent negative reply 
    140     /// 
    141     /// Second Digit (subject): 
    142     ///    x0x =             condition based on syntax 
    143     ///    x1x =             informational 
    144     ///    x2x =             connection 
    145     ///    x3x =             authentication/process 
    146     ///    x5x =             file system 
    147     char[3] code = "000"; 
    148  
    149     /// The message from the server. 
    150     /// 
    151     /// With some responses, the message may contain parseable information. 
    152     /// For example, this is true of the 257 response. 
    153     char[] message = null; 
     93struct FtpResponse { 
     94    /// The response code. 
     95    /// 
     96    /// The digits in the response code can be used to determine status 
     97    /// programatically. 
     98    /// 
     99    /// First Digit (status): 
     100    ///    1xx =             a positive, but preliminary, reply 
     101    ///    2xx =             a positive reply indicating completion 
     102    ///    3xx =             a positive reply indicating incomplete status 
     103    ///    4xx =             a temporary negative reply 
     104    ///    5xx =             a permanent negative reply 
     105    /// 
     106    /// Second Digit (subject): 
     107    ///    x0x =             condition based on syntax 
     108    ///    x1x =             informational 
     109    ///    x2x =             connection 
     110    ///    x3x =             authentication/process 
     111    ///    x5x =             file system 
     112    char[3] code = "000"; 
     113 
     114    /// The message from the server. 
     115    /// 
     116    /// With some responses, the message may contain parseable information. 
     117    /// For example, this is true of the 257 response. 
     118    char[] message = null; 
    154119} 
    155120 
    156121/// Active or passive connection mode. 
    157 enum FtpConnectionType 
    158 
    159     /// Active - server connects to client on open port. 
    160     active, 
    161     /// Passive - server listens for a connection from the client. 
    162     passive, 
     122enum FtpConnectionType { 
     123    /// Active - server connects to client on open port. 
     124    active, 
     125    /// Passive - server listens for a connection from the client. 
     126    passive, 
    163127} 
    164128 
     
    166130/// 
    167131/// This is used to properly send PORT and PASV commands. 
    168 struct FtpConnectionDetail 
    169 
    170     /// The type to be used. 
    171     FtpConnectionType type = FtpConnectionType.passive; 
    172  
    173     /// The address to give the server. 
    174     Address address = null; 
    175  
    176     /// The address to actually listen on. 
    177     Address listen = null; 
     132struct FtpConnectionDetail { 
     133    /// The type to be used. 
     134    FtpConnectionType type = FtpConnectionType.passive; 
     135 
     136    /// The address to give the server. 
     137    Address address = null; 
     138 
     139    /// The address to actually listen on. 
     140    Address listen = null; 
    178141} 
    179142 
    180143/// A supported feature of an FTP server. 
    181 struct FtpFeature 
    182 
    183     /// The command which is supported, e.g. SIZE. 
    184     char[] command = null; 
    185     /// Parameters for this command; e.g. facts for MLST. 
    186     char[] params = null; 
     144struct FtpFeature { 
     145    /// The command which is supported, e.g. SIZE. 
     146    char[] command = null; 
     147    /// Parameters for this command; e.g. facts for MLST. 
     148    char[] params = null; 
    187149} 
    188150 
    189151/// The type of a file in an FTP listing. 
    190 enum FtpFileType 
    191 
    192     /// An unknown file or type (no type fact.) 
    193     unknown, 
    194     /// A regular file, or similar. 
    195     file, 
    196     /// The current directory (e.g. ., but not necessarily.) 
    197     cdir, 
    198     /// A parent directory (usually "..".) 
    199     pdir, 
    200     /// Any other type of directory. 
    201     dir, 
    202     /// Another type of file.  Consult the "type" fact. 
    203     other, 
     152enum FtpFileType { 
     153    /// An unknown file or type (no type fact.) 
     154    unknown, 
     155    /// A regular file, or similar. 
     156    file, 
     157    /// The current directory (e.g. ., but not necessarily.) 
     158    cdir, 
     159    /// A parent directory (usually "..".) 
     160    pdir, 
     161    /// Any other type of directory. 
     162    dir, 
     163    /// Another type of file.  Consult the "type" fact. 
     164    other, 
    204165} 
    205166 
    206167/// Information about a file in an FTP listing. 
    207 struct FtpFileInfo 
    208 
    209     /// The filename. 
    210     char[] name = null; 
    211     /// Its type. 
    212     FtpFileType type = FtpFileType.unknown; 
    213     /// Size in bytes (8 bit octets), or -1 if not available. 
    214     long size = -1; 
    215     /// Modification time, if available. 
    216     Time modify = Time.max; 
    217     /// Creation time, if available (not often.) 
    218     Time create = Time.max; 
    219     /// The file's mime type, if known. 
    220     char[] mime = null; 
    221     /// An associative array of all facts returned by the server, lowercased. 
    222     char[][char[]] facts; 
    223 
    224  
    225 class FtpSocketConduit : SocketConduit 
    226 
    227     public this(Socket sock) 
    228     { 
    229         this.socket_ = sock; 
    230     } 
    231  
    232     void setSocket(Socket sock) 
    233     { 
    234         this.socket_ = sock; 
    235     } 
    236 
    237  
    238 /// A connection to an FTP server. 
    239 /// 
    240 /// Example: 
    241 /// ---------- 
    242 /// auto ftp = new FTPConnection("hostname", "user", "pass",21); 
    243 /// 
    244 /// ftp.mkdir("test"); 
    245 /// ftp.close(); 
    246 /// ---------- 
    247 /// 
    248 /// Standards:               RFC 959, RFC 2228, RFC 2389, RFC 2428 
    249 /// 
    250 /// Bugs: 
    251 ///    Does not support several uncommon FTP commands and responses. 
    252  
    253  
    254 class FTPConnection : Telnet 
    255 
    256     /// Supported features (if known.) 
    257     /// 
    258     /// This will be empty if not known, or else contain at least FEAT. 
    259     public FtpFeature[] supported_features = null; 
    260  
    261     /// Data connection information. 
    262     protected FtpConnectionDetail data_info; 
    263  
    264     /// The last-set restart position. 
    265     /// 
    266     /// This is only used when a local file is used for a RETR or STOR. 
    267     protected size_t restart_pos = 0; 
    268  
    269     /// error handler 
    270     protected void exception (char[] msg) 
    271     { 
    272         throw new FTPException ("Exception: " ~ msg); 
    273     } 
    274  
    275     /// ditto 
    276     protected void exception (FtpResponse r) 
    277     { 
    278         throw new FTPException (r); 
    279     } 
    280  
    281     /// Construct an FTPConnection without connecting immediately. 
    282     public this() 
    283     { 
    284     } 
    285  
    286     /// Connect to an FTP server with a username and password. 
    287     /// 
    288     /// Params: 
    289     ///    hostname =        the hostname or IP address to connect to 
    290     ///    port =            the port number to connect to 
    291     ///    username =        username to be sent 
    292     ///    password =        password to be sent, if requested 
    293     public this(char[] hostname, char[] username, char[] password, int port = 21) 
    294     { 
    295         this.connect(hostname, username, password,port); 
    296     } 
    297  
    298     /// Connect to an FTP server with a username and password. 
    299     /// 
    300     /// Params: 
    301     ///    hostname =        the hostname or IP address to connect to 
    302     ///    port =            the port number to connect to 
    303     ///    username =        username to be sent 
    304     ///    password =        password to be sent, if requested 
    305  
    306      
    307     this(FtpAddress fad) 
    308     { 
    309         this.connect(fad.address, fad.user, fad.pass, fad.port); 
    310     } 
    311      
    312     public void connect(FtpAddress fad) 
    313     { 
    314         this.connect(fad.address, fad.user, fad.pass, fad.port); 
    315     } 
    316  
    317     public void connect(char[] hostname, char[] username, char[] password, int port = 21) 
    318         in 
    319     { 
    320         // We definitely need a hostname and port. 
    321         assert (hostname.length > 0); 
    322         assert (port > 0); 
    323     } 
    324     body 
    325     { 
    326         // Close any active connection. 
    327  
    328         if (this.socket !is null) 
    329             this.close(); 
    330  
    331  
    332         // Connect to whichever FTP server responds first. 
    333         this.findAvailableServer(hostname, port); 
    334  
    335         this.socket.blocking = false; 
    336  
    337         scope (failure) 
    338             { 
    339                 this.close(); 
    340             } 
    341  
    342         // The welcome message should always be a 220.  120 and 421 are considered errors. 
    343         this.readResponse("220"); 
    344  
    345         if (username.length == 0) 
    346             return; 
    347  
    348         // Send the username.  Anything but 230, 331, or 332 is basically an error. 
    349         this.sendCommand("USER", username); 
    350         auto response = this.readResponse(); 
    351  
    352         // 331 means username okay, please proceed with password. 
    353         if (response.code == "331") 
    354             { 
    355                 this.sendCommand("PASS", password); 
    356                 response = this.readResponse(); 
    357             } 
    358  
    359         // We don't support ACCT (332) so we should get a 230 here. 
    360         if (response.code != "230" && response.code != "202") 
    361             { 
    362  
    363                 exception (response); 
    364             } 
    365  
    366     } 
    367  
    368     /// Close the connection to the server. 
    369     public void close() 
    370     { 
    371         assert (this.socket !is null); 
    372  
    373         // Don't even try to close it if it's not open. 
    374         if (this.socket !is null) 
    375             { 
    376                 try 
    377                     { 
    378                         this.sendCommand("QUIT"); 
    379                         this.readResponse("221"); 
    380                     } 
    381                 // Ignore if the above could not be completed. 
    382                 catch (FTPException) 
    383                     { 
    384                     } 
    385  
    386                 // Shutdown the socket... 
    387                 this.socket.shutdown(SocketShutdown.BOTH); 
    388                 this.socket.detach(); 
    389  
    390                 // Clear out everything. 
    391                 delete this.supported_features; 
    392                 delete this.socket; 
    393             } 
    394     } 
    395  
    396     /// Set the connection to use passive mode for data tranfers. 
    397     /// 
    398     /// This is the default. 
    399     public void setPassive() 
    400     { 
    401         this.data_info.type = FtpConnectionType.passive; 
    402  
    403         delete this.data_info.address; 
    404         delete this.data_info.listen; 
    405     } 
    406  
    407     /// Set the connection to use active mode for data transfers. 
    408     /// 
    409     /// This may not work behind firewalls. 
    410     /// 
    411     /// Params: 
    412     ///    ip =              the ip address to use 
    413     ///    port =            the port to use 
    414     ///    listen_ip =       the ip to listen on, or null for any 
    415     ///    listen_port =     the port to listen on, or 0 for the same port 
    416     public void setActive(char[] ip, ushort port, char[] listen_ip = null, ushort listen_port = 0) 
    417         in 
    418     { 
    419         assert (ip.length > 0); 
    420         assert (port > 0); 
    421     } 
    422     body 
    423     { 
    424         this.data_info.type = FtpConnectionType.active; 
    425         this.data_info.address = new IPv4Address(ip, port); 
    426  
    427         // A local-side port? 
    428         if (listen_port == 0) 
    429             listen_port = port; 
    430  
    431         // Any specific IP to listen on? 
    432         if (listen_ip is null) 
    433             this.data_info.listen = new IPv4Address(IPv4Address.ADDR_ANY, listen_port); 
    434         else 
    435             this.data_info.listen = new IPv4Address(listen_ip, listen_port); 
    436     } 
    437  
    438  
    439     /// Change to the specified directory. 
    440     public void cd(char[] dir) 
    441         in 
    442     { 
    443         assert (dir.length > 0); 
    444     } 
    445     body 
    446     { 
    447         this.sendCommand("CWD", dir); 
    448         this.readResponse("250"); 
    449     } 
    450  
    451     /// Change to the parent of this directory. 
    452     public void cdup() 
    453     { 
    454         this.sendCommand("CDUP"); 
    455         FtpResponse fr = this.readResponse(); 
     168struct FtpFileInfo { 
     169    /// The filename. 
     170    char[] name = null; 
     171    /// Its type. 
     172    FtpFileType type = FtpFileType.unknown; 
     173    /// Size in bytes (8 bit octets), or -1 if not available. 
     174    long size = -1; 
     175    /// Modification time, if available. 
     176    Time modify = Time.max; 
     177    /// Creation time, if available (not often.) 
     178    Time create = Time.max; 
     179    /// The file's mime type, if known. 
     180    char[] mime = null; 
     181    /// An associative array of all facts returned by the server, lowercased. 
     182    char[][char[]] facts; 
     183
     184 
     185class FtpException: Exception { 
     186    this(char[] msg) { 
     187        super(msg); 
     188    } 
     189
     190 
     191class FTPConnection: Telnet { 
     192 
     193    FtpFeature[] supportedFeatures_ = null; 
     194    FtpConnectionDetail inf_; 
     195    size_t restartPos_ = 0; 
     196    char[] currFile_ = ""; 
     197    SocketConduit dataSocket_; 
     198 
     199    public FtpFeature[] supportedFeatures() { 
     200        if(supportedFeatures_ !is null) { 
     201            return supportedFeatures_; 
     202        } 
     203        getFeatures(); 
     204        return supportedFeatures_; 
     205    } 
     206 
     207    private int toInt(char[] s) { 
     208        return cast(int) toLong(s); 
     209    } 
     210 
     211    private long toLong(char[] s) { 
     212        return Integer.parse(s); 
     213    } 
     214 
     215    void exception(char[] message) { 
     216        throw new FtpException(message); 
     217    } 
     218 
     219    void exception(FtpResponse fr) { 
     220        exception(fr.message); 
     221    } 
     222 
     223    public this() { 
     224 
     225    } 
     226 
     227    public this(char[] hostname, char[] username = "anonymous", 
     228                char[] password = "anonymous@anonymous", uint port = 21) { 
     229        this.connect(hostname, username, password, port); 
     230    } 
     231 
     232    public this(FtpAddress fad) { 
     233        connect(fad); 
     234    } 
     235 
     236    public void connect(FtpAddress fad) { 
     237        this.connect(fad.address, fad.user, fad.pass, fad.port); 
     238    } 
     239 
     240    public void connect(char[] hostname, char[] username = "anonymous", 
     241                        char[] password = "anonymous@anonymous", uint port = 21) 
     242    in { 
     243        // We definitely need a hostname and port. 
     244        assert(hostname.length > 0); 
     245        assert(port > 0); 
     246    } 
     247    body { 
     248 
     249        if(socket !is null) { 
     250            socket.close(); 
     251        } 
     252 
     253        this.findAvailableServer(hostname, port); 
     254 
     255        scope(failure) { 
     256            close(); 
     257        } 
     258 
     259        readResponse("220"); 
     260 
     261        if(username.length = 0) { 
     262            return; 
     263        } 
     264 
     265        sendCommand("USER", username); 
     266        FtpResponse response = readResponse(); 
     267 
     268        if(response.code == "331") { 
     269            sendCommand("PASS", password); 
     270            response = readResponse(); 
     271        } 
     272 
     273        if(response.code != "230" && response.code != "202") { 
     274            exception(response); 
     275        } 
     276    } 
     277 
     278    public void close() { 
     279        if(socket !is null) { 
     280            try { 
     281                sendCommand("QUIT"); 
     282                readResponse("221"); 
     283            } catch(FtpException) { 
     284 
     285            } 
     286 
     287            socket.close(); 
     288 
     289            delete supportedFeatures_; 
     290            delete socket; 
     291        } 
     292    } 
     293 
     294    public void setPassive() { 
     295        inf_.type = FtpConnectionType.passive; 
     296 
     297        delete inf_.address; 
     298        delete inf_.listen; 
     299    } 
     300 
     301    public void setActive(char[] ip, ushort port, char[] listen_ip = null, 
     302                          ushort listen_port = 0) 
     303    in { 
     304        assert(ip.length > 0); 
     305        assert(port > 0); 
     306    } 
     307    body { 
     308        inf_.type = FtpConnectionType.active; 
     309        inf_.address = new IPv4Address(ip, port); 
     310 
     311        // A local-side port? 
     312        if(listen_port == 0) 
     313            listen_port = port; 
     314 
     315        // Any specific IP to listen on? 
     316        if(listen_ip == null) 
     317            inf_.listen = new IPv4Address(IPv4Address.ADDR_ANY, listen_port); 
     318        else 
     319            inf_.listen = new IPv4Address(listen_ip, listen_port); 
     320    } 
     321 
     322    public void cd(char[] dir) 
     323    in { 
     324        assert(dir.length > 0); 
     325    } 
     326    body { 
     327        sendCommand("CWD", dir); 
     328        readResponse("250"); 
     329    } 
     330 
     331    public void cdup() { 
     332        sendCommand("CDUP"); 
     333        FtpResponse fr = readResponse(); 
    456334        if(fr.code == "200" || fr.code == "250") 
    457335            return; 
    458336        else 
    459337            exception(fr); 
    460     } 
    461  
    462     /// Determine the current directory. 
    463     /// 
    464     /// Returns:             the current working directory 
    465     public char[] cwd() 
    466     { 
    467         this.sendCommand("PWD"); 
    468         auto response = this.readResponse("257"); 
    469  
    470         return this.parse257(response); 
    471     } 
    472  
    473     /// Change the permissions of a file. 
    474     /// 
    475     /// This is a popular feature of most FTP servers, but not explicitly outlined 
    476     /// in the spec.  It does not work on, for example, Windows servers. 
    477     /// 
    478     /// Params: 
    479     ///    path =            the path to the file to chmod 
    480     ///    mode =            the desired mode; expected in octal (0777, 0644, etc.) 
    481     public void chmod(char[] path, int mode) 
    482         in 
    483     { 
    484         assert (path.length > 0); 
    485         assert (mode >= 0 && (mode >> 16) == 0); 
    486     } 
    487     body 
    488     { 
    489         char[] tmp = "000"; 
    490         // Convert our octal parameter to a string. 
    491         Integer.format(tmp, cast(long) mode, Integer.Style.Octal); 
    492         this.sendCommand("SITE CHMOD", tmp, path); 
    493         this.readResponse("200"); 
    494     } 
    495  
    496     /// Remove a file or directory. 
    497     /// 
    498     /// Params: 
    499     ///    path =            the path to the file or directory to delete 
    500     public void del(char[] path) 
    501         in 
    502     { 
    503         assert (path.length > 0); 
    504     } 
    505     body 
    506     { 
    507         this.sendCommand("DELE", path); 
    508         auto response = this.readResponse(); 
    509  
    510         // Try it as a directory, then...? 
    511         if (response.code != "250") 
    512             this.rm(path); 
    513     } 
    514  
    515     /// Remove a directory. 
    516     /// 
    517     /// Params: 
    518     ///    path =            the directory to delete 
    519     public void rm(char[] path) 
    520         in 
    521     { 
    522         assert (path.length > 0); 
    523     } 
    524     body 
    525     { 
    526         this.sendCommand("RMD", path); 
    527         this.readResponse("250"); 
    528     } 
    529  
    530     /// Rename/move a file or directory. 
    531     /// 
    532     /// Params: 
    533     ///    old_path =        the current path to the file 
    534     ///    new_path =        the new desired path 
    535     public void rename(char[] old_path, char[] new_path) 
    536         in 
    537     { 
    538         assert (old_path.length > 0); 
    539         assert (new_path.length > 0); 
    540     } 
    541     body 
    542     { 
    543         // Rename from... rename to.  Pretty simple. 
    544         this.sendCommand("RNFR", old_path); 
    545         this.readResponse("350"); 
    546  
    547         this.sendCommand("RNTO", new_path); 
    548         this.readResponse("250"); 
    549     } 
    550  
    551     ///returns 1 if it's a file 2 if it's a directory and 0 if it doesn't exist; contributed by Bobef 
    552     int exist(char[] file) 
    553     { 
     338    } 
     339 
     340    public char[] cwd() { 
     341        sendCommand("PWD"); 
     342        auto response = readResponse("257"); 
     343 
     344        return parse257(response); 
     345    } 
     346 
     347    public void chmod(char[] path, int mode) 
     348    in { 
     349        assert(path.length > 0); 
     350        assert(mode >= 0 && (mode >> 16) == 0); 
     351    } 
     352    body { 
     353        char[] tmp = "000"; 
     354        // Convert our octal parameter to a string. 
     355        Integer.format(tmp, cast(long) mode, Integer.Style.Octal); 
     356        sendCommand("SITE CHMOD", tmp, path); 
     357        readResponse("200"); 
     358    } 
     359 
     360    public void del(char[] path) 
     361    in { 
     362        assert(path.length > 0); 
     363    } 
     364    body { 
     365        sendCommand("DELE", path); 
     366        auto response = readResponse(); 
     367 
     368        // Try it as a directory, then...? 
     369        if(response.code != "250") 
     370            rm(path); 
     371    } 
     372 
     373    public void rm(char[] path) 
     374    in { 
     375        assert(path.length > 0); 
     376    } 
     377    body { 
     378        sendCommand("RMD", path); 
     379        readResponse("250"); 
     380    } 
     381 
     382    public void rename(char[] old_path, char[] new_path) 
     383    in { 
     384        assert(old_path.length > 0); 
     385        assert(new_path.length > 0); 
     386    } 
     387    body { 
     388        // Rename from... rename to.  Pretty simple. 
     389        sendCommand("RNFR", old_path); 
     390        readResponse("350"); 
     391 
     392        sendCommand("RNTO", new_path); 
     393        readResponse("250"); 
     394    } 
     395 
     396    int exist(char[] file) { 
     397        try { 
     398            auto fi = getFileInfo(file); 
     399            if(fi.type == FtpFileType.file) 
     400                return 1; 
     401            else if(fi.type == FtpFileType.dir || fi.type == FtpFileType.cdir || fi.type == FtpFileType.pdir) 
     402                return 2; 
     403        } catch(FTPException o) { 
     404            if(o.response_code != "501") 
     405                throw o; 
     406        } 
     407        return 0; 
     408    } 
     409 
     410    long size(char[] file) { 
    554411        try 
    555         { 
    556             auto fi=getFileInfo(file); 
    557             if(fi.type==FtpFileType.file) return 1; 
    558             else if(fi.type==FtpFileType.dir || fi.type==FtpFileType.cdir || fi.type==FtpFileType.pdir) return 2; 
    559         } 
    560         catch(FTPException o) {if(o.response_code!="501") throw o;} 
    561         return 0; 
    562     } 
    563  
    564     ///determines the size of a file; Contributed by Bobef 
    565     long size(char[] file) 
    566     { 
    567         try return getFileInfo(file).size; 
    568         catch(FTPException o) {if(o.response_code!="501") throw o;} 
     412        return getFileInfo(file).size; 
     413        catch(FTPException o) { 
     414            if(o.response_code != "501") 
     415                throw o; 
     416        } 
    569417        return -1; 
    570418    } 
    571419 
    572     /// Determine the size in bytes of a file. 
    573     /// 
    574     /// This size is dependent on the current type (ASCII or IMAGE.) 
    575     /// 
    576     /// Params: 
    577     ///    path =            the file to retrieve the size of 
    578     ///    format =          what format the size is desired in 
    579     public size_t size(char[] path, FtpFormat format = FtpFormat.image) 
    580         in 
    581     { 
    582         assert (path.length > 0); 
    583     } 
    584     body 
    585     { 
    586         this.type(format); 
    587  
    588         this.sendCommand("SIZE", path); 
    589         auto response = this.readResponse("213"); 
    590  
    591         // Only try to parse the numeric bytes of the response. 
    592         size_t end_pos = 0; 
    593         while (end_pos < response.message.length) 
    594             { 
    595                 if (response.message[end_pos] < '0' || response.message[end_pos] > '9') 
    596                     break; 
    597                 end_pos++; 
    598             } 
    599  
    600         return toInt(response.message[0 .. end_pos]); 
    601     } 
    602  
    603     /// Send a command and process the data socket. 
    604     /// 
    605     /// This opens the data connection and checks for the appropriate response. 
    606     /// 
    607     /// Params: 
    608     ///    command =         the command to send (e.g. STOR) 
    609     ///    parameters =      any arguments to send 
    610     /// 
    611     /// Returns:             the data socket 
    612     public Socket processDataCommand(char[] command, char[][] parameters ...) 
    613     { 
    614         // Create a connection. 
    615         Socket data = this.getDataSocket(); 
    616         scope (failure) 
    617             { 
    618                 // Close the socket, whether we were listening or not. 
    619                 data.shutdown(SocketShutdown.BOTH); 
    620                 data.detach(); 
    621             } 
    622  
    623         // Tell the server about it. 
    624         this.sendCommand(command, parameters); 
    625  
    626         // We should always get a 150/125 response. 
    627         auto response = this.readResponse(); 
    628         if (response.code != "150" && response.code != "125") 
    629             exception (response); 
    630  
    631         // We might need to do this for active connections. 
    632         this.prepareDataSocket(data); 
    633  
    634         return data; 
    635     } 
    636  
    637     /// Clean up after the data socket and process the response. 
    638     /// 
    639     /// This closes the socket and reads the 226 response. 
    640     /// 
    641     /// Params: 
    642     ///    data =            the data socket 
    643     public void finishDataCommand(Socket data) 
    644     { 
    645         // Close the socket.  This tells the server we're done (EOF.) 
    646         data.shutdown(SocketShutdown.BOTH); 
    647         data.detach(); 
    648  
    649         // We shouldn't get a 250 in STREAM mode. 
    650         this.readResponse("226"); 
    651     } 
    652  
    653     /// Get a data socket from the server. 
    654     /// 
    655     /// This sends PASV/PORT as necessary. 
    656     /// 
    657     /// Returns:             the data socket or a listener 
    658     protected Socket getDataSocket() 
    659     { 
    660         // What type are we using? 
    661         switch (this.data_info.type) 
    662             { 
    663             default: 
    664                 exception ("unknown connection type"); 
    665  
    666                 // Passive is complicated.  Handle it in another member. 
    667             case FtpConnectionType.passive: 
    668                 return this.connectPassive(); 
    669  
    670                 // Active is simpler, but not as fool-proof. 
    671             case FtpConnectionType.active: 
    672                 IPv4Address data_addr = cast(IPv4Address) this.data_info.address; 
    673  
    674                 // Start listening. 
    675                 Socket listener = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP); 
    676                 listener.bind(this.data_info.listen); 
    677                 listener.listen(32); 
    678  
    679                 // Use EPRT if we know it's supported. 
    680                 if (this.is_supported("EPRT")) 
    681                     { 
    682                         char[64] tmp = void; 
    683  
    684                         this.sendCommand("EPRT", Text.layout(tmp, "|1|%0|%1|", data_addr.toAddrString, data_addr.toPortString)); 
    685                         // this.sendCommand("EPRT", format("|1|%s|%s|", data_addr.toAddrString(), data_addr.toPortString())); 
    686                         this.readResponse("200"); 
    687                     } 
    688                 else 
    689                     { 
    690                         int h1, h2, h3, h4, p1, p2; 
    691                         h1 = (data_addr.addr() >> 24) % 256; 
    692                         h2 = (data_addr.addr() >> 16) % 256; 
    693                         h3 = (data_addr.addr() >> 8_) % 256; 
    694                         h4 = (data_addr.addr() >> 0_) % 256; 
    695                         p1 = (data_addr.port() >> 8_) % 256; 
    696                         p2 = (data_addr.port() >> 0_) % 256; 
    697  
    698                         // low overhead method to format a numerical string 
    699                         char[64] tmp = void; 
    700                         char[20] foo = void; 
    701                         auto str = Text.layout (tmp, "%0,%1,%2,%3,%4,%5", 
    702                                                 Integer.format(foo[0..3], h1), 
    703                                                 Integer.format(foo[3..6], h2), 
    704                                                 Integer.format(foo[6..9], h3), 
    705                                                 Integer.format(foo[9..12], h4), 
    706                                                 Integer.format(foo[12..15], p1), 
    707                                                 Integer.format(foo[15..18], p2)); 
    708  
    709                         // This formatting is weird. 
    710                         // this.sendCommand("PORT", format("%d,%d,%d,%d,%d,%d", h1, h2, h3, h4, p1, p2)); 
    711  
    712                         this.sendCommand("PORT", str); 
    713                         this.readResponse("200"); 
    714                     } 
    715  
    716                 return listener; 
    717             } 
    718         assert (false); 
    719     } 
    720  
    721     /// Prepare a data socket for use. 
    722     /// 
    723     /// This modifies the socket in some cases. 
    724     /// 
    725     /// Params: 
    726     ///    data =            the data listener socket 
    727     protected void prepareDataSocket(inout Socket data) 
    728     { 
    729         switch (this.data_info.type) 
    730             { 
    731             default: 
    732                 exception ("unknown connection type"); 
    733  
    734             case FtpConnectionType.active: 
    735                 Socket new_data = null; 
    736  
    737                 SocketSet set = new SocketSet(); 
    738                 scope (exit) 
    739                     delete set; 
    740  
    741                 // At end_time, we bail. 
    742                 Time end_time = Clock.now + this.timeout; 
    743  
    744                 while (Clock.now < end_time) 
    745                     { 
    746                         set.reset(); 
    747                         set.add(data); 
    748  
    749                         // Can we accept yet? 
    750                         int code = Socket.select(set, null, null, this.timeout); 
    751                         if (code == -1 || code == 0) 
    752                             break; 
    753  
    754                         new_data = data.accept(); 
    755                         break; 
    756                     } 
    757  
    758                 if (new_data is null) 
    759                     throw new FTPException("CLIENT: No connection from server", "420"); 
    760  
    761                 // We don't need the listener anymore. 
    762                 data.shutdown(SocketShutdown.BOTH); 
    763                 data.detach(); 
    764  
    765                 // This is the actual socket. 
    766                 data = new_data; 
    767                 break; 
    768  
    769             case FtpConnectionType.passive: 
    770                 break; 
    771             } 
    772     } 
    773