| 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(); |
|---|
| | 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 | } |
|---|