1c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair/*
2c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * Copyright 2008 the original author or authors.
3c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair *
4c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * Licensed under the Apache License, Version 2.0 (the "License");
5c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * you may not use this file except in compliance with the License.
6c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * You may obtain a copy of the License at
7c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair *
8c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair *      http://www.apache.org/licenses/LICENSE-2.0
9c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair *
10c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * Unless required by applicable law or agreed to in writing, software
11c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * distributed under the License is distributed on an "AS IS" BASIS,
12c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * See the License for the specific language governing permissions and
14c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * limitations under the License.
15c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair */
16c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairpackage org.mockftpserver.core.util;
17c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
18c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairimport org.mockftpserver.core.CommandSyntaxException;
19c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairimport org.mockftpserver.core.MockFtpServerException;
20c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
21c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairimport java.net.InetAddress;
22c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairimport java.net.UnknownHostException;
23c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairimport java.util.Arrays;
24c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairimport java.util.List;
25c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
26c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair/**
27c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * Utility class for parsing port values from command arguments.
28c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair *
29c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * @author Chris Mair
30c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair * @version $Revision$ - $Date$
31c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair */
32c1de24f1bfa6699e54b069e300af5e4246b34a34chrismairpublic final class PortParser {
33c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
34c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    /**
35c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * Private constructor. All methods are static.
36c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     */
37c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    private PortParser() {
38c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    }
39c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
40c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    /**
41c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * Parse a 32-bit IP address from the String[] of FTP command parameters.
42c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *
43c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @param parameters - the String[] of command parameters. It is the concatenation
44c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   of a 32-bit internet host address and a 16-bit TCP port address. This address
45c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   information is broken into 8-bit fields and the value of each field is encoded
46c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   as a separate parameter whose value is a decimal number (in character string
47c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   representation).  Thus, the six parameters for the port command would be:
48c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   h1,h2,h3,h4,p1,p2
49c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   where h1 is the high order 8 bits of the internet host address, and p1 is the
50c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   high order 8 bits of the port number.
51c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @return the InetAddres representing the host parsed from the parameters
52c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
53c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                               - if parameters is null or contains an insufficient number of elements
54c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @throws NumberFormatException - if one of the parameters does not contain a parsable integer
55c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     */
56c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    public static InetAddress parseHost(String[] parameters) {
57c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        verifySufficientParameters(parameters);
58c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
59c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        byte host1 = parseByte(parameters[0]);
60c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        byte host2 = parseByte(parameters[1]);
61c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        byte host3 = parseByte(parameters[2]);
62c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        byte host4 = parseByte(parameters[3]);
63c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
64c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        byte[] address = {host1, host2, host3, host4};
65c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        InetAddress inetAddress = null;
66c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        try {
67c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair            inetAddress = InetAddress.getByAddress(address);
68c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        }
69c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        catch (UnknownHostException e) {
70c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair            throw new MockFtpServerException("Error parsing host", e);
71c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        }
72c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
73c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        return inetAddress;
74c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    }
75c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
76c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    /**
77c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * Parse a 16-bit port number from the String[] of FTP command parameters.
78c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *
79c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @param parameters - the String[] of command parameters. It is the concatenation
80c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   of a 32-bit internet host address and a 16-bit TCP port address. This address
81c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   information is broken into 8-bit fields and the value of each field is encoded
82c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   as a separate parameter whose value is a decimal number (in character string
83c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   representation).  Thus, the six parameters for the port command would be:
84c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   h1,h2,h3,h4,p1,p2
85c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   where h1 is the high order 8 bits of the internet host address, and p1 is the
86c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                   high order 8 bits of the port number.
87c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @return the port number parsed from the parameters
88c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
89c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *                               - if parameters is null or contains an insufficient number of elements
90c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @throws NumberFormatException - if one of the parameters does not contain a parsable integer
91c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     */
92c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    public static int parsePortNumber(String[] parameters) {
93c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        verifySufficientParameters(parameters);
94c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
95c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        int port1 = Integer.parseInt(parameters[4]);
96c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        int port2 = Integer.parseInt(parameters[5]);
97c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        int port = (port1 << 8) + port2;
98c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
99c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        return port;
100c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    }
101c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
102c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    /**
103c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * Verify that the parameters is not null and contains the required number of elements
104c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *
105c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @param parameters - the String[] of command parameters
106c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @throws CommandSyntaxException - if parameters is null or contains an insufficient number of elements
107c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     */
108c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    private static void verifySufficientParameters(String[] parameters) {
109c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        if (parameters == null || parameters.length < 6) {
110c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair            List parms = parameters == null ? null : Arrays.asList(parameters);
111c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair            throw new CommandSyntaxException("The PORT command must contain least be 6 parameters: " + parms);
112c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        }
113c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    }
114c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
115c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    /**
116c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * Convert the InetAddess and port number to a comma-delimited list of byte values,
117c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * suitable for the response String from the PASV command.
118c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *
119c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @param host - the InetAddress
120c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @param port - the port number
121c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @return the comma-delimited list of byte values, e.g., "196,168,44,55,23,77"
122c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     */
123c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    public static String convertHostAndPortToCommaDelimitedBytes(InetAddress host, int port) {
124c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        StringBuffer buffer = new StringBuffer();
125c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        byte[] address = host.getAddress();
126c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        for (int i = 0; i < address.length; i++) {
127c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair            int positiveValue = (address[i] >= 0) ? address[i] : 256 + address[i];
128c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair            buffer.append(positiveValue);
129c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair            buffer.append(",");
130c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        }
131c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        int p1 = port >> 8;
132c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        int p2 = port % 256;
133c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        buffer.append(String.valueOf(p1));
134c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        buffer.append(",");
135c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        buffer.append(String.valueOf(p2));
136c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        return buffer.toString();
137c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    }
138c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
139c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    /**
140c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * Parse the specified String as an unsigned decimal byte value (i.e., 0..255). We can't just use
141c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * Byte.parseByte(string) because that parses the string as a signed byte.
142c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     *
143c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @param string - the String containing the decimal byte representation to be parsed
144c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     * @return the byte value
145c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair     */
146c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    private static byte parseByte(String string) {
147c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair        return (byte) (0xFF & Short.parseShort(string));
148c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair    }
149c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair
150c1de24f1bfa6699e54b069e300af5e4246b34a34chrismair}