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