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                      if (IDN.toASCII(host).length() > 255) {
79                          throw new IllegalArgumentException(host + " IDN: " +
80                                  IDN.toASCII(host) + " exceeds 255 char limit");
81                      }
82                      break;
83                  case IPv6:
84                      if (!NetUtil.isValidIpV6Address(host)) {
85                          throw new IllegalArgumentException(host + " is not a valid IPv6 address");
86                      }
87                      break;
88                  case UNKNOWN:
89                      break;
90              }
91              host = IDN.toASCII(host);
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 io.netty.handler.codec.socks.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 io.netty.handler.codec.socks.SocksCmdType}
126      *         or null when there was no host specified during response construction
127      */
128     public String host() {
129         if (host != null) {
130             return IDN.toUnicode(host);
131         } else {
132             return null;
133         }
134     }
135 
136     /**
137      * Returns port that is used as a parameter in {@link io.netty.handler.codec.socks.SocksCmdType}.
138      * Port (BND.PORT field in response) is port that the server assigned to connect to the target host.
139      *
140      * @return port that is used as a parameter in {@link io.netty.handler.codec.socks.SocksCmdType}
141      */
142     public int port() {
143         return port;
144     }
145 
146     @Override
147     public void encodeAsByteBuf(ByteBuf byteBuf) {
148         byteBuf.writeByte(protocolVersion().byteValue());
149         byteBuf.writeByte(cmdStatus.byteValue());
150         byteBuf.writeByte(0x00);
151         byteBuf.writeByte(addressType.byteValue());
152         switch (addressType) {
153             case IPv4: {
154                 byte[] hostContent = host == null ?
155                         IPv4_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
156                 byteBuf.writeBytes(hostContent);
157                 byteBuf.writeShort(port);
158                 break;
159             }
160             case DOMAIN: {
161                 byte[] hostContent = host == null ?
162                         DOMAIN_ZEROED : host.getBytes(CharsetUtil.US_ASCII);
163                 byteBuf.writeByte(hostContent.length);   // domain length
164                 byteBuf.writeBytes(hostContent);   // domain value
165                 byteBuf.writeShort(port);  // port value
166                 break;
167             }
168             case IPv6: {
169                 byte[] hostContent = host == null
170                         ? IPv6_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
171                 byteBuf.writeBytes(hostContent);
172                 byteBuf.writeShort(port);
173                 break;
174             }
175         }
176     }
177 }