View Javadoc
1   /*
2    * Copyright 2015 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  package io.netty.handler.codec.dns;
17  
18  import io.netty.util.internal.PlatformDependent;
19  import io.netty.util.internal.StringUtil;
20  
21  import java.net.IDN;
22  
23  import static io.netty.util.internal.ObjectUtil.checkNotNull;
24  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
25  
26  /**
27   * A skeletal implementation of {@link DnsRecord}.
28   */
29  public abstract class AbstractDnsRecord implements DnsRecord {
30  
31      private final String name;
32      private final DnsRecordType type;
33      private final short dnsClass;
34      private final long timeToLive;
35      private int hashCode;
36  
37      /**
38       * Creates a new {@link #CLASS_IN IN-class} record.
39       *
40       * @param name the domain name
41       * @param type the type of the record
42       * @param timeToLive the TTL value of the record
43       */
44      protected AbstractDnsRecord(String name, DnsRecordType type, long timeToLive) {
45          this(name, type, CLASS_IN, timeToLive);
46      }
47  
48      /**
49       * Creates a new record.
50       *
51       * @param name the domain name
52       * @param type the type of the record
53       * @param dnsClass the class of the record, usually one of the following:
54       *                 <ul>
55       *                     <li>{@link #CLASS_IN}</li>
56       *                     <li>{@link #CLASS_CSNET}</li>
57       *                     <li>{@link #CLASS_CHAOS}</li>
58       *                     <li>{@link #CLASS_HESIOD}</li>
59       *                     <li>{@link #CLASS_NONE}</li>
60       *                     <li>{@link #CLASS_ANY}</li>
61       *                 </ul>
62       * @param timeToLive the TTL value of the record
63       */
64      protected AbstractDnsRecord(String name, DnsRecordType type, int dnsClass, long timeToLive) {
65          checkPositiveOrZero(timeToLive, "timeToLive");
66          // Convert to ASCII which will also check that the length is not too big.
67          // See:
68          //   - https://github.com/netty/netty/issues/4937
69          //   - https://github.com/netty/netty/issues/4935
70          this.name = appendTrailingDot(IDNtoASCII(name));
71          this.type = checkNotNull(type, "type");
72          this.dnsClass = (short) dnsClass;
73          this.timeToLive = timeToLive;
74      }
75  
76      private static String IDNtoASCII(String name) {
77          checkNotNull(name, "name");
78          if (PlatformDependent.isAndroid() && DefaultDnsRecordDecoder.ROOT.equals(name)) {
79              // Prior Android 10 there was a bug that did not correctly parse ".".
80              //
81              // See https://github.com/netty/netty/issues/10034
82              return name;
83          }
84          return IDN.toASCII(name);
85      }
86  
87      private static String appendTrailingDot(String name) {
88          if (name.length() > 0 && name.charAt(name.length() - 1) != '.') {
89              return name + '.';
90          }
91          return name;
92      }
93  
94      @Override
95      public String name() {
96          return name;
97      }
98  
99      @Override
100     public DnsRecordType type() {
101         return type;
102     }
103 
104     @Override
105     public int dnsClass() {
106         return dnsClass & 0xFFFF;
107     }
108 
109     @Override
110     public long timeToLive() {
111         return timeToLive;
112     }
113 
114     @Override
115     public boolean equals(Object obj) {
116         if (this == obj) {
117             return true;
118         }
119 
120         if (!(obj instanceof DnsRecord)) {
121             return false;
122         }
123 
124         final DnsRecord that = (DnsRecord) obj;
125         final int hashCode = this.hashCode;
126         if (hashCode != 0 && hashCode != that.hashCode()) {
127             return false;
128         }
129 
130         return type().intValue() == that.type().intValue() &&
131                dnsClass() == that.dnsClass() &&
132                name().equals(that.name());
133     }
134 
135     @Override
136     public int hashCode() {
137         final int hashCode = this.hashCode;
138         if (hashCode != 0) {
139             return hashCode;
140         }
141 
142         return this.hashCode = name.hashCode() * 31 + type().intValue() * 31 + dnsClass();
143     }
144 
145     @Override
146     public String toString() {
147         StringBuilder buf = new StringBuilder(64);
148 
149         buf.append(StringUtil.simpleClassName(this))
150            .append('(')
151            .append(name())
152            .append(' ')
153            .append(timeToLive())
154            .append(' ');
155 
156         DnsMessageUtil.appendRecordClass(buf, dnsClass())
157                       .append(' ')
158                       .append(type().name())
159                       .append(')');
160 
161         return buf.toString();
162     }
163 }