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

Changeset 3467

Show
Ignore:
Timestamp:
04/27/08 07:49:51 (7 months ago)
Author:
lmartin92
Message:

I believe now that data connections are working and now we are just waiting for me to add the rest and come up with a way to directly access the Input/Output Streams(SocketConduits?) in a very (for lack of better words) good way

Files:

Legend:

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

    r3466 r3467  
    11/** 
    2  * Author: Lester L. Martin II 
    3  * Copyright (c) Lester L. Martin II 
     2 * Author:          Lester L. Martin II 
     3 * Copyright:      (c) Lester L. Martin II 
    44 * Based upon prior FtpClient.d (some code is borrowed) 
    5  * License: $(BSD Liscense) 
     5 * License:         $(BSD Liscense) 
     6 * Initial realse:  August 8, 2008 /+this may be incorrect and is not an approximation+/ 
    67 */ 
    78 
     
    2425    import tango.time.chrono.Gregorian; 
    2526    import tango.io.GrowBuffer; 
     27    import  Timestamp = tango.text.convert.TimeStamp; 
     28    import tango.core.Array; 
     29    debug(UnitTest) 
     30    { 
     31        import tango.io.Stdout; 
     32    } 
    2633} 
    2734 
     
    478485    } 
    479486 
    480     Time* modified(char[] file) 
     487    Time modified(char[] file) 
    481488    in { 
    482489        assert(file.length > 0); 
     
    487494 
    488495        // The whole response should be a timeval. 
    489         return &this.parseTimeval(response.message); 
     496        return this.parseTimeval(response.message); 
    490497    } 
    491498 
     
    544551 
    545552    public void sendCommand(char[] command, char[][] parameters...) { 
    546         assert(this.socket_ !is null); 
    547553 
    548554        char[] socketCommand = command; 
     
    723729    /// 
    724730    /// Returns:             a connected socket 
    725     public SocketConduit connectPassive() { 
    726         Address connect_to = null; 
    727  
    728         // SPSV, which is just a port number. 
    729         if(this.is_supported("SPSV")) { 
    730             this.sendCommand("SPSV"); 
    731             auto response = this.readResponse("227"); 
    732  
    733             // Connecting to the same host. 
    734             IPv4Address 
    735             remote = cast(IPv4Address) this.socket_.socket.remoteAddress(); 
    736             assert(remote !is null); 
    737  
    738             uint address = remote.addr(); 
    739             uint port = toInt(response.message); 
    740  
    741             connect_to = new IPv4Address(address, cast(ushort) port); 
    742         } 
    743         // Extended passive mode (IP v6, etc.) 
    744         else if(this.is_supported("EPSV")) { 
    745             this.sendCommand("EPSV"); 
    746             auto response = this.readResponse("229"); 
    747  
    748             // Try to pull out the (possibly not parenthesized) address. 
    749             auto r = Regex(`\([^0-9][^0-9][^0-9](\d+)[^0-9]\)`); 
    750             if(!r.test(response.message)) 
    751                 throw new FtpException("CLIENT: Unable to parse address", "501"); 
    752  
    753             IPv4Address 
    754             remote = cast(IPv4Address) this.socket_.socket.remoteAddress(); 
    755             assert(remote !is null); 
    756  
    757             uint address = remote.addr(); 
    758             uint port = toInt(r.match(1)); 
    759  
    760             connect_to = new IPv4Address(address, cast(ushort) port); 
    761         } else { 
    762             this.sendCommand("PASV"); 
    763             auto response = this.readResponse("227"); 
    764  
    765             // Try to pull out the (possibly not parenthesized) address. 
    766             auto r = Regex( 
    767             `(\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(,\s*(\d+))?`); 
    768             if(!r.test(response.message)) 
    769                 throw new FtpException("CLIENT: Unable to parse address", "501"); 
    770  
    771             // Now put it into something std.socket will understand. 
    772             char[] 
    773                  address = r.match(1) ~ "." ~ r.match(2) ~ "." ~ r.match(3) ~ "." ~ r.match( 
    774                      4); 
    775             uint 
    776             port = (toInt(r.match(5)) << 8) + (r.match(7).length > 0 ? toInt( 
    777                 r.match(7)) : 0); 
    778  
    779             // Okay, we've got it! 
    780             connect_to = new IPv4Address(address, port); 
    781         } 
    782  
    783         scope(exit) 
    784         delete connect_to; 
    785  
    786         // This will throw an exception if it cannot connect. 
    787         SocketConduit sock = new SocketConduit(); 
     731    public SocketConduit connectPassive() 
     732    { 
     733        Address connect_to = null; 
     734 
     735        // SPSV, which is just a port number. 
     736        if (this.is_supported("SPSV")) 
     737            { 
     738                this.sendCommand("SPSV"); 
     739                auto response = this.readResponse("227"); 
     740 
     741                // Connecting to the same host. 
     742                IPv4Address remote = cast(IPv4Address) this.socket_.socket.remoteAddress(); 
     743                assert (remote !is null); 
     744 
     745                uint address = remote.addr(); 
     746                uint port = toInt(response.message); 
     747 
     748                connect_to = new IPv4Address(address, cast(ushort) port); 
     749            } 
     750        // Extended passive mode (IP v6, etc.) 
     751        else if (this.is_supported("EPSV")) 
     752            { 
     753                this.sendCommand("EPSV"); 
     754                auto response = this.readResponse("229"); 
     755 
     756                // Try to pull out the (possibly not parenthesized) address. 
     757                auto r = Regex(`\([^0-9][^0-9][^0-9](\d+)[^0-9]\)`); 
     758                debug(UnitTest) 
     759                { 
     760                    Stdout(response.message).newline.flush; 
     761                    Stdout(response.message[0 .. find(response.message, '\n') ]).newline.flush; 
     762                } 
     763                if ( !r.test(response.message[0 .. find(response.message, '\n') ])) 
     764                    throw new FtpException("CLIENT: Unable to parse address", "501"); 
     765 
     766                IPv4Address remote = cast(IPv4Address) this.socket_.socket.remoteAddress(); 
     767                assert (remote !is null); 
     768 
     769                uint address = remote.addr(); 
     770                uint port = toInt(r.match(1)); 
     771 
     772                connect_to = new IPv4Address(address, cast(ushort) port); 
     773            } 
     774        else 
     775            { 
     776                this.sendCommand("PASV"); 
     777                auto response = this.readResponse("227"); 
     778 
     779                debug(UnitTest) 
     780                { 
     781                    Stdout(response.message).newline.flush; 
     782                    Stdout(response.message[0 .. find(response.message, '\n') ]).newline.flush; 
     783                } 
     784                // Try to pull out the (possibly not parenthesized) address. 
     785                auto r = Regex(`(\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(,\s*(\d+))?`); 
     786                if ( !r.test(response.message[0 .. find(response.message, '\n') ]) ) 
     787                    throw new FtpException("CLIENT: Unable to parse address", "501"); 
     788 
     789                // Now put it into something std.socket will understand. 
     790                char[] address = r.match(1)~"."~r.match(2)~"."~r.match(3)~"."~r.match(4); 
     791                uint port = (toInt(r.match(5)) << 8) + (r.match(7).length > 0 ? toInt(r.match(7)) : 0); 
     792 
     793                // Okay, we've got it! 
     794                connect_to = new IPv4Address(address, port); 
     795            } 
     796 
     797        scope (exit) 
     798            delete connect_to; 
     799 
     800        // This will throw an exception if it cannot connect. 
     801        SocketConduit sock = new SocketConduit(); 
    788802        sock.connect(connect_to); 
    789803        return sock; 
    790     } 
     804    } 
     805    /* 
     806    SocketConduit sock = new SocketConduit(); 
     807        sock.connect(connect_to); 
     808        return sock; 
     809        */ 
    791810 
    792811    public bool isSupported(char[] command) 
     
    864883        } 
    865884    } 
     885 
     886    public void finishDataCommand(SocketConduit data) 
     887    { 
     888        // Close the socket.  This tells the server we're done (EOF.) 
     889        data.close(); 
     890        data.detach(); 
     891 
     892        // We shouldn't get a 250 in STREAM mode. 
     893        this.readResponse("226"); 
     894    } 
     895     
     896    public SocketConduit processDataCommand(char[] command, char[][] parameters ...) 
     897    { 
     898        // Create a connection. 
     899        SocketConduit data = this.getDataSocket(); 
     900        scope (failure) 
     901            { 
     902                // Close the socket, whether we were listening or not. 
     903                data.close(); 
     904            } 
     905 
     906        // Tell the server about it. 
     907        this.sendCommand(command, parameters); 
     908 
     909        // We should always get a 150/125 response. 
     910        auto response = this.readResponse(); 
     911        if (response.code != "150" && response.code != "125") 
     912            exception (response); 
     913 
     914        // We might need to do this for active connections. 
     915        this.prepareDataSocket(data); 
     916 
     917        return data; 
     918    } 
     919 
     920        public FtpFileInfo[] ls(char[] path = "") // default to current dir 
     921        in 
     922    { 
     923        assert (path.length == 0 || path[path.length - 1] != '/'); 
     924    } 
     925    body 
     926    { 
     927        FtpFileInfo[] dir; 
     928 
     929        // We'll try MLSD (which is so much better) first... but it may fail. 
     930        bool mlsd_success = false; 
     931        SocketConduit data = null; 
     932 
     933        // Try it if it could/might/maybe is supported. 
     934        /*if (this.isSupported("MLST")) 
     935            { 
     936                mlsd_success = true; 
     937 
     938                // Since this is a data command, processDataCommand handles 
     939                // checking the response... just catch its Exception. 
     940                try 
     941                    { 
     942                        if (path.length > 0) 
     943                            data = this.processDataCommand("MLSD", path); 
     944                        else 
     945                            data = this.processDataCommand("MLSD"); 
     946                    } 
     947                catch (FtpException) 
     948                    mlsd_success = false; 
     949            } 
     950 
     951        // If it passed, parse away! 
     952        if (mlsd_success) 
     953            { 
     954                auto listing = new GrowBuffer; 
     955                this.readStream(data, listing); 
     956                this.finishDataCommand(data); 
     957 
     958                // Each line is something in that directory. 
     959                char[][] lines = Text.splitLines (cast(char[]) listing.slice()); 
     960                scope (exit) 
     961                    delete lines; 
     962 
     963                foreach (char[] line; lines) 
     964                    { 
     965                        // Parse each line exactly like MLST does. 
     966                        FtpFileInfo info = this.parseMlstLine(line); 
     967                        if (info.name.length > 0) 
     968                            dir ~= info; 
     969                    } 
     970 
     971                return dir; 
     972            } 
     973        // Fall back to LIST. 
     974        else*/ 
     975            return this.sendListCommand(path); 
     976    } 
     977 
     978       protected void readStream(SocketConduit data, OutputStream stream, FtpProgress progress = null) 
     979        in 
     980    { 
     981        assert (data !is null); 
     982        assert (stream !is null); 
     983    } 
     984    body 
     985    { 
     986        // Set up a SocketSet so we can use select() - it's pretty efficient. 
     987        SocketSet set = new SocketSet(); 
     988        scope (exit) 
     989            delete set; 
     990 
     991        // At end_time, we bail. 
     992        Time end_time = Clock.now + this.timeout; 
     993 
     994        // This is the buffer the stream data is stored in. 
     995        ubyte[8 * 1024] buf; 
     996        int buf_size = 0; 
     997 
     998        bool completed = false; 
     999        size_t pos; 
     1000        while (Clock.now < end_time) 
     1001            { 
     1002                set.reset(); 
     1003                set.add(data.socket); 
     1004 
     1005                // Can we read yet, can we read yet? 
     1006                int code = Socket.select(set, null, null, this.timeout); 
     1007                if (code == -1 || code == 0) 
     1008                    break; 
     1009 
     1010                buf_size = data.socket.receive(buf); 
     1011                if (buf_size == data.socket.ERROR) 
     1012                    break; 
     1013 
     1014                if (buf_size == 0) 
     1015                    { 
     1016                        completed = true; 
     1017                        break; 
     1018                    } 
     1019 
     1020                stream.write(buf[0 .. buf_size]); 
     1021 
     1022                pos += buf_size; 
     1023                if (progress !is null) 
     1024                    progress(pos); 
     1025 
     1026                // Give it more time as long as data is going through. 
     1027                end_time = Clock.now + this.timeout; 
     1028            } 
     1029 
     1030        // Did all the data get received? 
     1031        if (!completed) 
     1032            throw new FtpException("CLIENT: Timeout when reading data", "420"); 
     1033    } 
     1034 
     1035    protected void sendStream(SocketConduit data, InputStream stream, FtpProgress progress = null) 
     1036        in 
     1037    { 
     1038        assert (data !is null); 
     1039        assert (stream !is null); 
     1040    } 
     1041    body 
     1042    { 
     1043        // Set up a SocketSet so we can use select() - it's pretty efficient. 
     1044        SocketSet set = new SocketSet(); 
     1045        scope (exit) 
     1046            delete set; 
     1047 
     1048        // At end_time, we bail. 
     1049        Time end_time = Clock.now + this.timeout; 
     1050 
     1051        // This is the buffer the stream data is stored in. 
     1052        ubyte[8 * 1024] buf; 
     1053        size_t buf_size = 0, buf_pos = 0; 
     1054        int delta = 0; 
     1055 
     1056        size_t pos = 0; 
     1057        bool completed = false; 
     1058        while (!completed && Clock.now < end_time) 
     1059            { 
     1060                set.reset(); 
     1061                set.add(data.socket); 
     1062 
     1063                // Can we write yet, can we write yet? 
     1064                int code = Socket.select(null, set, null, this.timeout); 
     1065                if (code == -1 || code == 0) 
     1066                    break; 
     1067 
     1068                if (buf_size - buf_pos <= 0) 
     1069                    { 
     1070                        if ((buf_size = stream.read(buf)) is stream.Eof) 
     1071                            buf_size = 0, completed = true; 
     1072                        buf_pos = 0; 
     1073                    } 
     1074 
     1075                // Send the chunk (or as much of it as possible!) 
     1076                delta = data.socket.send(buf[buf_pos .. buf_size]); 
     1077                if (delta == data.socket.ERROR) 
     1078                    break; 
     1079 
     1080                buf_pos += delta; 
     1081 
     1082                pos += delta; 
     1083                if (progress !is null) 
     1084                    progress(pos); 
     1085 
     1086                // Give it more time as long as data is going through. 
     1087                if (delta != 0) 
     1088                    end_time = Clock.now + this.timeout; 
     1089            } 
     1090 
     1091        // Did all the data get sent? 
     1092        if (!completed) 
     1093            throw new FtpException("CLIENT: Timeout when sending data", "420"); 
     1094    } 
     1095 
     1096    protected FtpFileInfo[] sendListCommand(char[] path) 
     1097    { 
     1098        FtpFileInfo[] dir; 
     1099        SocketConduit data = null; 
     1100 
     1101        if (path.length > 0) 
     1102            data = this.processDataCommand("LIST", path); 
     1103        else 
     1104            data = this.processDataCommand("LIST"); 
     1105 
     1106        // Read in the stupid non-standardized response. 
     1107        auto listing = new GrowBuffer; 
     1108        this.readStream(data, listing); 
     1109        this.finishDataCommand(data); 
     1110 
     1111        // Split out the lines.  Most of the time, it's one-to-one. 
     1112        char[][] lines = Text.splitLines (cast(char[]) listing.slice()); 
     1113        scope (exit) 
     1114            delete lines; 
     1115 
     1116        foreach (char[] line; lines) 
     1117            { 
     1118                // If there are no spaces, or if there's only one... skip the line. 
     1119                // This is probably like a "total 8" line. 
     1120                if (Text.locate(line, ' ') == Text.locatePrior(line, ' ')) 
     1121                    continue; 
     1122 
     1123                // Now parse the line, or try to. 
     1124                FtpFileInfo info = this.parseListLine(line); 
     1125                if (info.name.length > 0) 
     1126                    dir ~= info; 
     1127            } 
     1128 
     1129        return dir; 
     1130    } 
     1131     
     1132    protected FtpFileInfo parseListLine(char[] line) 
     1133    { 
     1134        FtpFileInfo info; 
     1135        size_t pos = 0; 
     1136 
     1137        // Convenience function to parse a word from the line. 
     1138        char[] parse_word() 
     1139            { 
     1140                size_t start = 0, end = 0; 
     1141 
     1142                // Skip whitespace before. 
     1143                while (pos < line.length && line[pos] == ' ') 
     1144                    pos++; 
     1145 
     1146                start = pos; 
     1147                while (pos < line.length && line[pos] != ' ') 
     1148                    pos++; 
     1149                end = pos; 
     1150 
     1151                // Skip whitespace after. 
     1152                while (pos < line.length && line[pos] == ' ') 
     1153                    pos++; 
     1154 
     1155                return line[start .. end]; 
     1156            } 
     1157 
     1158        // We have to sniff this... :/. 
     1159        switch (! Text.contains ("0123456789", line[0])) 
     1160            { 
     1161                // Not a number; this is UNIX format. 
     1162            case true: 
     1163                // The line must be at least 20 characters long. 
     1164                if (line.length < 20) 
     1165                    return info; 
     1166 
     1167                // The first character tells us what it is. 
     1168                if (line[0] == 'd') 
     1169                    info.type = FtpFileType.dir; 
     1170                else if (line[0] == '-') 
     1171                    info.type = FtpFileType.file; 
     1172                else 
     1173                    info.type = FtpFileType.unknown; 
     1174 
     1175                // Parse out the mode... rwxrwxrwx = 777. 
     1176                char[] unix_mode = "0000".dup; 
     1177                void read_mode(int digit) 
     1178                    { 
     1179                        for (pos = 1 + digit * 3; pos <= 3 + digit * 3; pos++) 
     1180                            { 
     1181                                if (line[pos] == 'r') 
     1182                                    unix_mode[digit + 1] |= 4; 
     1183                                else if (line[pos] == 'w') 
     1184                                    unix_mode[digit + 1] |= 2; 
     1185                                else if (line[pos] == 'x') 
     1186                                    unix_mode[digit + 1] |= 1; 
     1187                            } 
     1188                    } 
     1189 
     1190                // This makes it easier, huh? 
     1191                read_mode(0); 
     1192                read_mode(1); 
     1193                read_mode(2); 
     1194 
     1195                info.facts["UNIX.mode"] = unix_mode; 
     1196 
     1197                // Links, owner, group.  These are hard to translate to MLST facts. 
     1198                parse_word(); 
     1199                parse_word(); 
     1200                parse_word(); 
     1201 
     1202                // Size in bytes, this one is good. 
     1203                info.size = toLong(parse_word()); 
     1204 
     1205                // Make sure we still have enough space. 
     1206                if (pos + 13 >= line.length) 
     1207                    return info; 
     1208 
     1209                // Not parsing date for now.  It's too weird (last 12 months, etc.) 
     1210                pos += 13; 
     1211 
     1212                info.name = line[pos .. line.length]; 
     1213                break; 
     1214 
     1215                // A number; this is DOS format. 
     1216            case false: 
     1217                // We need some data here, to parse. 
     1218                if (line.length < 18) 
     1219                    return info; 
     1220 
     1221                // The order is 1 MM, 2 DD, 3 YY, 4 HH, 5 MM, 6 P 
     1222                auto r = Regex(`(\d\d)-(\d\d)-(\d\d)\s+(\d\d):(\d\d)(A|P)M`); 
     1223                if ( r.test(line) ) 
     1224                    return info; 
     1225 
     1226                if (Timestamp.dostime (r.match(0), info.modify) is 0) 
     1227                    info.modify = Time.max; 
     1228 
     1229                pos = r.match(0).length; 
     1230                delete r; 
     1231 
     1232                // This will either be <DIR>, or a number. 
     1233                char[] dir_or_size = parse_word(); 
     1234 
     1235                if (dir_or_size.length < 0) 
     1236                    return info; 
     1237                else if (dir_or_size[0] == '<') 
     1238                    info.type = FtpFileType.dir; 
     1239                else 
     1240                    info.size = toLong(dir_or_size); 
     1241 
     1242                info.name = line[pos .. line.length]; 
     1243                break; 
     1244 
     1245                // Something else, not supported. 
     1246            default: 
     1247                throw new FtpException("CLIENT: Unsupported LIST format", "501"); 
     1248            } 
     1249 
     1250        // Try to fix the type? 
     1251        if (info.name == ".") 
     1252            info.type = FtpFileType.cdir; 
     1253        else if (info.name == "..") 
     1254            info.type = FtpFileType.pdir; 
     1255 
     1256        return info; 
     1257    } 
     1258     
     1259    protected FtpFileInfo parseMlstLine(char[] line) 
     1260    { 
     1261        FtpFileInfo info; 
     1262 
     1263        // After this loop, filename_pos will be location of space + 1. 
     1264        size_t filename_pos = 0; 
     1265        while (filename_pos < line.length && line[filename_pos++] != ' ') 
     1266            continue; 
     1267 
     1268        if (filename_pos == line.length) 
     1269            throw new FtpException("CLIENT: Bad syntax in MLSx response", "501"); 
     1270 
     1271        info.name = line[filename_pos .. line.length]; 
     1272 
     1273        // Everything else is frosting on top. 
     1274        if (filename_pos > 1) 
     1275            { 
     1276                char[][] temp_facts = Text.delimit(line[0 .. filename_pos - 1], ";"); 
     1277 
     1278                // Go through each fact and parse them into the array. 
     1279                foreach (char[] fact; temp_facts) 
     1280                    { 
     1281                        int pos = Text.locate(fact, '='); 
     1282                        if (pos == fact.length) 
     1283                            continue; 
     1284 
     1285                        info.facts[Ascii.toLower(fact[0 .. pos])] = fact[pos + 1 .. fact.length]; 
     1286                    } 
     1287 
     1288                // Do we have a type? 
     1289                if ("type" in info.facts) 
     1290                    { 
     1291                        // Some reflection might be nice here. 
     1292                        switch (Ascii.toLower(info.facts["type"])) 
     1293                            { 
     1294                            case "file": 
     1295                                info.type = FtpFileType.file; 
     1296                                break; 
     1297 
     1298                            case "cdir": 
     1299                                info.type = FtpFileType.cdir; 
     1300                                break; 
     1301 
     1302                            case "pdir": 
     1303                                info.type = FtpFileType.pdir; 
     1304                                break; 
     1305 
     1306                            case "dir": 
     1307                                info.type = FtpFileType.dir; 
     1308                                break; 
     1309 
     1310                            default: 
     1311                                info.type = FtpFileType.other; 
     1312                            } 
     1313                    } 
     1314 
     1315                // Size, mime, etc... 
     1316                if ("size" in info.facts) 
     1317                    info.size = toLong(info.facts["size"]); 
     1318                if ("media-type" in info.facts) 
     1319                    info.mime = info.facts["media-type"]; 
     1320 
     1321                // And the two dates. 
     1322                if ("modify" in info.facts) 
     1323                    info.modify = this.parseTimeval(info.facts["modify"]); 
     1324                if ("create" in info.facts) 
     1325                    info.create = this.parseTimeval(info.facts["create"]); 
     1326            } 
     1327 
     1328        return info; 
     1329    } 
    8661330} 
    8671331 
     
    8911355        conn.rename("ServingSchedule.tif", "No"); 
    8921356        Stdout("Getting the mod date.").newline.flush; 
    893         Time modified = *(conn.modified("No")); 
     1357        Time modified = conn.modified("No"); 
    8941358        Stdout("The file was modified: "); 
    8951359        Stdout(layout ("{:ddd, dd MMMM yyyy HH:mm:ss z}", modified)).newline.flush; 
     
    8981362        Stdout("Deleting the file No.").newline.flush; 
    8991363        conn.del("No"); 
     1364        FtpFileInfo[] infs = conn.ls(); 
     1365        foreach(FtpFileInfo inf; infs) 
     1366        { 
     1367            Stdout(inf.name).newline.flush; 
     1368        } 
    9001369        conn.close(); 
    9011370        Stdout("Finished.").newline.flush;