View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.socks;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.util.CharsetUtil;
20  import io.netty.util.NetUtil;
21  
22  import java.net.IDN;
23  
24  /**
25   * A socks cmd response.
26   *
27   * @see SocksCmdRequest
28   * @see SocksCmdResponseDecoder
29   */
30  public final class SocksCmdResponse extends SocksResponse {
31      private final SocksCmdStatus cmdStatus;
32  
33      private final SocksAddressType addressType;
34      private final String host;
35      private final int port;
36  
37      // All arrays are initialized on construction time to 0/false/null remove array Initialization
38      private static final byte[] DOMAIN_ZEROED = {0x00};
39      private static final byte[] IPv4_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00};
40      private static final byte[] IPv6_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00,
41              0x00, 0x00, 0x00, 0x00,
42              0x00, 0x00, 0x00, 0x00,
43              0x00, 0x00, 0x00, 0x00};
44  
45      public SocksCmdResponse(SocksCmdStatus cmdStatus, SocksAddressType addressType) {
46          this(cmdStatus, addressType, null, 0);
47      }
48  
49      /**
50       * Constructs new response and includes provided host and port as part of it.
51       *
52       * @param cmdStatus status of the response
53       * @param addressType type of host parameter
54       * @param host host (BND.ADDR field) is address that server used when connecting to the target host.
55       *             When null a value of 4/8 0x00 octets will be used for IPv4/IPv6 and a single 0x00 byte will be
56       *             used for domain addressType. Value is converted to ASCII using {@link IDN#toASCII(String)}.
57       * @param port port (BND.PORT field) that the server assigned to connect to the target host
58       * @throws NullPointerException in case cmdStatus or addressType are missing
59       * @throws IllegalArgumentException in case host or port cannot be validated
60       * @see IDN#toASCII(String)
61       */
62      public SocksCmdResponse(SocksCmdStatus cmdStatus, SocksAddressType addressType, String host, int port) {
63          super(SocksResponseType.CMD);
64          if (cmdStatus == null) {
65              throw new NullPointerException("cmdStatus");
66          }
67          if (addressType == null) {
68              throw new NullPointerException("addressType");
69          }
70          if (host != null) {
71              switch (addressType) {
72                  case IPv4:
73                      if (!NetUtil.isValidIpV4Address(host)) {
74                          throw new IllegalArgumentException(host + " is not a valid IPv4 address");
75                      }
76                      break;
77                  case DOMAIN:
78                      String asciiHost = IDN.toASCII(host);
79                      if (asciiHost.length() > 255) {
80                          throw new IllegalArgumentException(host + " IDN: " + asciiHost + " exceeds 255 char limit");
81                      }
82                      host = asciiHost;
83                      break;
84                  case IPv6:
85                      if (!NetUtil.isValidIpV6Address(host)) {
86                          throw new IllegalArgumentException(host + " is not a valid IPv6 address");
87                      }
88                      break;
89                  case UNKNOWN:
90                      break;
91              }
92          }
93          if (port < 0 || port > 65535) {
94              throw new IllegalArgumentException(port + " is not in bounds 0 <= x <= 65535");
95          }
96          this.cmdStatus = cmdStatus;
97          this.addressType = addressType;
98          this.host = host;
99          this.port = port;
100     }
101 
102     /**
103      * Returns the {@link SocksCmdStatus} of this {@link SocksCmdResponse}
104      *
105      * @return The {@link SocksCmdStatus} of this {@link SocksCmdResponse}
106      */
107     public SocksCmdStatus cmdStatus() {
108         return cmdStatus;
109     }
110 
111     /**
112      * Returns the {@link SocksAddressType} of this {@link SocksCmdResponse}
113      *
114      * @return The {@link SocksAddressType} of this {@link SocksCmdResponse}
115      */
116     public SocksAddressType addressType() {
117         return addressType;
118     }
119 
120     /**
121      * Returns host that is used as a parameter in {@link SocksCmdType}.
122      * Host (BND.ADDR field in response) is address that server used when connecting to the target host.
123      * This is typically different from address which client uses to connect to the SOCKS server.
124      *
125      * @return host that is used as a parameter in {@link SocksCmdType}
126      *         or null when there was no host specified during response construction
127      */
128     public String host() {
129         return host != null && addressType == SocksAddressType.DOMAIN ? IDN.toUnicode(host) : host;
130     }
131 
132     /**
133      * Returns port that is used as a parameter in {@link SocksCmdType}.
134      * Port (BND.PORT field in response) is port that the server assigned to connect to the target host.
135      *
136      * @return port that is used as a parameter in {@link SocksCmdType}
137      */
138     public int port() {
139         return port;
140     }
141 
142     @Override
143     public void encodeAsByteBuf(ByteBuf byteBuf) {
144         byteBuf.writeByte(protocolVersion().byteValue());
145         byteBuf.writeByte(cmdStatus.byteValue());
146         byteBuf.writeByte(0x00);
147         byteBuf.writeByte(addressType.byteValue());
148         switch (addressType) {
149             case IPv4: {
150                 byte[] hostContent = host == null ?
151                         IPv4_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
152                 byteBuf.writeBytes(hostContent);
153                 byteBuf.writeShort(port);
154                 break;
155             }
156             case DOMAIN: {
157                 if (host != null) {
158                     byteBuf.writeByte(host.length());
159                     byteBuf.writeCharSequence(host, CharsetUtil.US_ASCII);
160                 } else {
161                     byteBuf.writeByte(DOMAIN_ZEROED.length);
162                     byteBuf.writeBytes(DOMAIN_ZEROED);
163                 }
164                 byteBuf.writeShort(port);
165                 break;
166             }
167             case IPv6: {
168                 byte[] hostContent = host == null
169                         ? IPv6_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
170                 byteBuf.writeBytes(hostContent);
171                 byteBuf.writeShort(port);
172                 break;
173             }
174         }
175     }
176 }