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;
191845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismairimport org.mockftpserver.core.MockFtpServerException;
2047fb67a4e600f339064de4c08f10279accc95e92chrismair
21d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairimport java.net.InetAddress;
22d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairimport java.net.UnknownHostException;
23d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairimport java.util.Arrays;
2447fb67a4e600f339064de4c08f10279accc95e92chrismairimport java.util.List;
25d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
26d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair/**
273e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair * Utility class for parsing host and port values from command arguments.
28d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair *
29d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * @author Chris Mair
30d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair * @version $Revision$ - $Date$
31d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair */
32d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismairpublic final class PortParser {
33d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
34d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    /**
353e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * Parse the host address and port number of an extended address. This encoded format is used by
363e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * the EPRT FTP command, and supports IPv6.
373e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * <p/>
383e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * The client network address can be in IPv4 format (e.g., "132.235.1.2") or
393e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * IPv6 format (e.g., "1080::8:800:200C:417A"). See RFC2428 for more information.
403e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     *
413e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * @param parameter - the single parameter String containing the encoded host and port number
423e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * @return the populated HostAndPort object
43d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     */
443e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair    public static HostAndPort parseExtendedAddressHostAndPort(String parameter) {
453e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        if (parameter == null || parameter.length() == 0) {
463e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair            throw new CommandSyntaxException("The parameter string must not be empty or null");
473e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        }
483e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair
493e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        String delimiter = parameter.substring(0,1);
503e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        String[] tokens = parameter.split("\\" + delimiter);
513e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair
523e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        if (tokens.length < 4) {
533e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair            throw new CommandSyntaxException("Error parsing host and port number [" + parameter + "]");
543e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        }
553e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair
563e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        int port = Integer.parseInt(tokens[3]);
573e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair
583e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        InetAddress host;
593e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        try {
603e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair            host = InetAddress.getByName(tokens[2]);
613e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        }
623e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        catch (UnknownHostException e) {
633e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair            throw new CommandSyntaxException("Error parsing host [" + tokens[2] + "]", e);
643e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        }
653e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair
66899f9bd64499ce2a3f8492399e4910f27a8e1e4achrismair        return new HostAndPort(host, port);
67d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    }
68d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
69d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    /**
70899f9bd64499ce2a3f8492399e4910f27a8e1e4achrismair     * Parse a 32-bit IP address and 16-bit port number from the String[] of FTP command parameters.
71899f9bd64499ce2a3f8492399e4910f27a8e1e4achrismair     * This is used by the FTP "PORT" command.
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.
81899f9bd64499ce2a3f8492399e4910f27a8e1e4achrismair     * @return the HostAndPort object with the host InetAddres and int port parsed from the parameters
82d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
831845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair     *                               - if parameters is null or contains an insufficient number of elements
841845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair     * @throws NumberFormatException - if one of the parameters does not contain a parsable integer
85d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair     */
86899f9bd64499ce2a3f8492399e4910f27a8e1e4achrismair    public static HostAndPort parseHostAndPort(String[] parameters) {
87d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        verifySufficientParameters(parameters);
88d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
898d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host1 = parseByte(parameters[0]);
908d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host2 = parseByte(parameters[1]);
918d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host3 = parseByte(parameters[2]);
928d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        byte host4 = parseByte(parameters[3]);
93d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
94d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        byte[] address = {host1, host2, host3, host4};
951845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair        InetAddress inetAddress = null;
961845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair        try {
971845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair            inetAddress = InetAddress.getByAddress(address);
981845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair        }
991845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair        catch (UnknownHostException e) {
1001845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair            throw new MockFtpServerException("Error parsing host", e);
1011845e1232d9bfd95dcb223ca3ffa7a5b49dda85echrismair        }
102d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
103d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        int port1 = Integer.parseInt(parameters[4]);
104d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        int port2 = Integer.parseInt(parameters[5]);
105d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair        int port = (port1 << 8) + port2;
106d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
107899f9bd64499ce2a3f8492399e4910f27a8e1e4achrismair        return new HostAndPort(inetAddress, port);
108d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    }
109d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair
110d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair    /**
1115aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * Convert the InetAddess and port number to a comma-delimited list of byte values,
1125aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * suitable for the response String from the PASV command.
1135aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     *
1145aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * @param host - the InetAddress
1155aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * @param port - the port number
1165aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     * @return the comma-delimited list of byte values, e.g., "196,168,44,55,23,77"
1175aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair     */
1185aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair    public static String convertHostAndPortToCommaDelimitedBytes(InetAddress host, int port) {
1195aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        StringBuffer buffer = new StringBuffer();
1205aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        byte[] address = host.getAddress();
1215aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        for (int i = 0; i < address.length; i++) {
1225aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair            int positiveValue = (address[i] >= 0) ? address[i] : 256 + address[i];
1235aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair            buffer.append(positiveValue);
1245aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair            buffer.append(",");
1255aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        }
1265aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        int p1 = port >> 8;
1275aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        int p2 = port % 256;
1285aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        buffer.append(String.valueOf(p1));
1295aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        buffer.append(",");
1305aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        buffer.append(String.valueOf(p2));
1315aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair        return buffer.toString();
1325aee6e3edf4d3d8decff73d2a5a26cebdd0572f5chrismair    }
1338d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair
1348d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair    /**
1353e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * Verify that the parameters is not null and contains the required number of elements
1363e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     *
1373e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * @param parameters - the String[] of command parameters
1383e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * @throws CommandSyntaxException - if parameters is null or contains an insufficient number of elements
1393e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     */
1403e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair    private static void verifySufficientParameters(String[] parameters) {
1413e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        if (parameters == null || parameters.length < 6) {
1423e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair            List parms = parameters == null ? null : Arrays.asList(parameters);
1433e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair            throw new CommandSyntaxException("The PORT command must contain least be 6 parameters: " + parms);
1443e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair        }
1453e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair    }
1463e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair
1473e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair    /**
1488d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * Parse the specified String as an unsigned decimal byte value (i.e., 0..255). We can't just use
1498d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * Byte.parseByte(string) because that parses the string as a signed byte.
1508d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     *
1518d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * @param string - the String containing the decimal byte representation to be parsed
1528d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     * @return the byte value
1538d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair     */
1548d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair    private static byte parseByte(String string) {
1558d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair        return (byte) (0xFF & Short.parseShort(string));
1568d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair    }
1578d76c14c2b733a4cb61da029a527fc36ca4ac51cchrismair
1583e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair    /**
1593e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     * Private constructor. All methods are static.
1603e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair     */
1613e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair    private PortParser() {
1623e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair    }
1633e469b93fd10bc09ea2c088516168bf6a5cbaa43chrismair
164d8cb70b52ca9f78d526ccd9cf67e4f4345ecdc95chrismair}