View Javadoc
1   /*
2    * Copyright 2019 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    *   https://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  
17  package io.netty.handler.codec.dns;
18  
19  import io.netty.buffer.ByteBuf;
20  import io.netty.buffer.ByteBufUtil;
21  import io.netty.handler.codec.CorruptedFrameException;
22  import io.netty.util.CharsetUtil;
23  
24  import static io.netty.handler.codec.dns.DefaultDnsRecordDecoder.*;
25  
26  final class DnsCodecUtil {
27      private DnsCodecUtil() {
28          // Util class
29      }
30  
31      static void encodeDomainName(String name, ByteBuf buf) {
32          if (ROOT.equals(name)) {
33              // Root domain
34              buf.writeByte(0);
35              return;
36          }
37  
38          final String[] labels = name.split("\\.");
39          for (String label : labels) {
40              final int labelLen = label.length();
41              if (labelLen == 0) {
42                  // zero-length label means the end of the name.
43                  break;
44              }
45  
46              buf.writeByte(labelLen);
47              ByteBufUtil.writeAscii(buf, label);
48          }
49  
50          buf.writeByte(0); // marks end of name field
51      }
52  
53      static String decodeDomainName(ByteBuf in) {
54          int position = -1;
55          int checked = 0;
56          final int end = in.writerIndex();
57          final int readable = in.readableBytes();
58  
59          // Looking at the spec we should always have at least enough readable bytes to read a byte here but it seems
60          // some servers do not respect this for empty names. So just workaround this and return an empty name in this
61          // case.
62          //
63          // See:
64          // - https://github.com/netty/netty/issues/5014
65          // - https://www.ietf.org/rfc/rfc1035.txt , Section 3.1
66          if (readable == 0) {
67              return ROOT;
68          }
69  
70          final StringBuilder name = new StringBuilder(readable << 1);
71          while (in.isReadable()) {
72              final int len = in.readUnsignedByte();
73              final boolean pointer = (len & 0xc0) == 0xc0;
74              if (pointer) {
75                  if (position == -1) {
76                      position = in.readerIndex() + 1;
77                  }
78  
79                  if (!in.isReadable()) {
80                      throw new CorruptedFrameException("truncated pointer in a name");
81                  }
82  
83                  final int next = (len & 0x3f) << 8 | in.readUnsignedByte();
84                  if (next >= end) {
85                      throw new CorruptedFrameException("name has an out-of-range pointer");
86                  }
87                  in.readerIndex(next);
88  
89                  // check for loops
90                  checked += 2;
91                  if (checked >= end) {
92                      throw new CorruptedFrameException("name contains a loop.");
93                  }
94              } else if (len != 0) {
95                  if (!in.isReadable(len)) {
96                      throw new CorruptedFrameException("truncated label in a name");
97                  }
98                  name.append(in.toString(in.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
99                  in.skipBytes(len);
100             } else { // len == 0
101                 break;
102             }
103         }
104 
105         if (position != -1) {
106             in.readerIndex(position);
107         }
108 
109         if (name.length() == 0) {
110             return ROOT;
111         }
112 
113         if (name.charAt(name.length() - 1) != '.') {
114             name.append('.');
115         }
116 
117         return name.toString();
118     }
119 
120     /**
121      * Decompress pointer data.
122      * @param compression compressed data
123      * @return decompressed data
124      */
125     static ByteBuf decompressDomainName(ByteBuf compression) {
126         String domainName = decodeDomainName(compression);
127         ByteBuf result = compression.alloc().buffer(domainName.length() << 1);
128         encodeDomainName(domainName, result);
129         return result;
130     }
131 }