PortParser.java revision 8d76c14c2b733a4cb61da029a527fc36ca4ac51c
1d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair/*
2d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * Copyright 2008 the original author or authors.
3d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair *
4d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * Licensed under the Apache License, Version 2.0 (the "License");
5d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * you may not use this file except in compliance with the License.
6d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * You may obtain a copy of the License at
7d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair *
8d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair *      http://www.apache.org/licenses/LICENSE-2.0
9d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair *
10d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * Unless required by applicable law or agreed to in writing, software
11d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * distributed under the License is distributed on an "AS IS" BASIS,
12d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * See the License for the specific language governing permissions and
14d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * limitations under the License.
15d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair */
16d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairpackage org.mockftpserver.core.util;
17d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
1847fb67a4e600f339064de4c08f10279accc95e92chrismairimport org.mockftpserver.core.CommandSyntaxException;
1947fb67a4e600f339064de4c08f10279accc95e92chrismair
20d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairimport java.net.InetAddress;
21d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairimport java.net.UnknownHostException;
22d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairimport java.util.Arrays;
2347fb67a4e600f339064de4c08f10279accc95e92chrismairimport java.util.List;
24d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
25d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair/**
26d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * Utility class for parsing port values from command arguments.
27d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair *
28d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * @author Chris Mair
29d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * @version $Revision$ - $Date$
30d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair */
31d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairpublic final class PortParser {
32d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
33d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    /**
34d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * Private constructor. All methods are static.
35d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     */
36d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    private PortParser() {
37d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    }
38d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
39d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    /**
40d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * Parse a 32-bit IP address from the String[] of FTP command parameters.
41d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *
42d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @param parameters - the String[] of command parameters. It is the concatenation
43d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   of a 32-bit internet host address and a 16-bit TCP port address. This address
44d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   information is broken into 8-bit fields and the value of each field is encoded
45d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   as a separate parameter whose value is a decimal number (in character string
46d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   representation).  Thus, the six parameters for the port command would be:
47d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   h1,h2,h3,h4,p1,p2
48d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   where h1 is the high order 8 bits of the internet host address, and p1 is the
49d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   high order 8 bits of the port number.
50d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @return the InetAddres representing the host parsed from the parameters
51d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
52d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                                       - if parameters is null or contains an insufficient number of elements
53d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @throws NumberFormatException         - if one of the parameters does not contain a parsable integer
54d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @throws java.net.UnknownHostException
55d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     */
56d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    public static InetAddress parseHost(String[] parameters) throws UnknownHostException {
57d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        verifySufficientParameters(parameters);
58d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
598d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host1 = parseByte(parameters[0]);
608d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host2 = parseByte(parameters[1]);
618d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host3 = parseByte(parameters[2]);
628d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host4 = parseByte(parameters[3]);
63d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
64d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        byte[] address = {host1, host2, host3, host4};
65d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        InetAddress inetAddress = InetAddress.getByAddress(address);
66d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
67d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        return inetAddress;
68d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    }
69d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
70d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    /**
71d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * Parse a 16-bit port number from the String[] of FTP command parameters.
72d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *
73d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @param parameters - the String[] of command parameters. It is the concatenation
74d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   of a 32-bit internet host address and a 16-bit TCP port address. This address
75d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   information is broken into 8-bit fields and the value of each field is encoded
76d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   as a separate parameter whose value is a decimal number (in character string
77d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   representation).  Thus, the six parameters for the port command would be:
78d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   h1,h2,h3,h4,p1,p2
79d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   where h1 is the high order 8 bits of the internet host address, and p1 is the
80d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                   high order 8 bits of the port number.
81d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @return the port number parsed from the parameters
82d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
83d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *                               - if parameters is null or contains an insufficient number of elements
84d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @throws NumberFormatException - if one of the parameters does not contain a parsable integer
85d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     */
86d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    public static int parsePortNumber(String[] parameters) {
87d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        verifySufficientParameters(parameters);
88d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
89d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        int port1 = Integer.parseInt(parameters[4]);
90d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        int port2 = Integer.parseInt(parameters[5]);
91d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        int port = (port1 << 8) + port2;
92d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
93d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        return port;
94d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    }
95d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
96d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    /**
97d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * Verify that the parameters is not null and contains the required number of elements
98d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     *
99d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @param parameters - the String[] of command parameters
10047fb67a4e600f339064de4c08f10279accc95e92chrismair     * @throws CommandSyntaxException - if parameters is null or contains an insufficient number of elements
101d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     */
102d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    private static void verifySufficientParameters(String[] parameters) {
10347fb67a4e600f339064de4c08f10279accc95e92chrismair        if (parameters == null || parameters.length < 6) {
10447fb67a4e600f339064de4c08f10279accc95e92chrismair            List parms = parameters == null ? null : Arrays.asList(parameters);
10547fb67a4e600f339064de4c08f10279accc95e92chrismair            throw new CommandSyntaxException("The PORT command must contain least be 6 parameters: " + parms);
10647fb67a4e600f339064de4c08f10279accc95e92chrismair        }
107d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    }
108d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
1095aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair    /**
1105aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * Convert the InetAddess and port number to a comma-delimited list of byte values,
1115aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * suitable for the response String from the PASV command.
1125aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     *
1135aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * @param host - the InetAddress
1145aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * @param port - the port number
1155aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * @return the comma-delimited list of byte values, e.g., "196,168,44,55,23,77"
1165aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     */
1175aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair    public static String convertHostAndPortToCommaDelimitedBytes(InetAddress host, int port) {
1185aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        StringBuffer buffer = new StringBuffer();
1195aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        byte[] address = host.getAddress();
1205aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        for (int i = 0; i < address.length; i++) {
1215aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair            int positiveValue = (address[i] >= 0) ? address[i] : 256 + address[i];
1225aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair            buffer.append(positiveValue);
1235aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair            buffer.append(",");
1245aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        }
1255aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        int p1 = port >> 8;
1265aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        int p2 = port % 256;
1275aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        buffer.append(String.valueOf(p1));
1285aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        buffer.append(",");
1295aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        buffer.append(String.valueOf(p2));
1305aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        return buffer.toString();
1315aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair    }
1328d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair
1338d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair    /**
1348d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * Parse the specified String as an unsigned decimal byte value (i.e., 0..255). We can't just use
1358d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * Byte.parseByte(string) because that parses the string as a signed byte.
1368d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     *
1378d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * @param string - the String containing the decimal byte representation to be parsed
1388d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * @return the byte value
1398d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     */
1408d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair    private static byte parseByte(String string) {
1418d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        return (byte) (0xFF & Short.parseShort(string));
1428d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair    }
1438d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair
144d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair}