001/* 002 * Copyright 2015-2018 the original author or authors 003 * 004 * This software is licensed under the Apache License, Version 2.0, 005 * the GNU Lesser General Public License version 2 or later ("LGPL") 006 * and the WTFPL. 007 * You may choose either license to govern your use of this software only 008 * upon the condition that you accept all of the terms of either 009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. 010 */ 011package org.minidns.dnsname; 012 013import java.io.ByteArrayOutputStream; 014import java.io.DataInputStream; 015import java.io.IOException; 016import java.io.OutputStream; 017import java.io.Serializable; 018import java.util.Arrays; 019import java.util.HashSet; 020import java.util.Locale; 021 022import org.minidns.dnslabel.DnsLabel; 023import org.minidns.idna.MiniDnsIdna; 024 025/** 026 * A DNS name, also called "domain name". A DNS name consists of multiple 'labels' and is subject to certain restrictions (see 027 * for example <a href="https://tools.ietf.org/html/rfc3696#section-2">RFC 3696 § 2.</a>). 028 * <p> 029 * Instances of this class can be created by using {@link #from(String)}. 030 * </p> 031 * <p> 032 * This class holds three representations of a DNS name: ACE, raw ACE and IDN. ACE (ASCII Compatible Encoding), which 033 * can be accessed via {@link #ace}, represents mostly the data that got send over the wire. But since DNS names are 034 * case insensitive, the ACE value is normalized to lower case. You can use {@link #getRawAce()} to get the raw ACE data 035 * that was received, which possibly includes upper case characters. The IDN (Internationalized Domain Name), that is 036 * the DNS name as it should be shown to the user, can be retrieved using {@link #asIdn()}. 037 * </p> 038 * 039 * @see <a href="https://tools.ietf.org/html/rfc3696">RFC 3696</a> 040 * @author Florian Schmaus 041 * 042 */ 043public class DnsName implements CharSequence, Serializable, Comparable<DnsName> { 044 045 /** 046 * 047 */ 048 private static final long serialVersionUID = 1L; 049 050 /** 051 * @see <a href="https://www.ietf.org/rfc/rfc3490.txt">RFC 3490 § 3.1 1.</a> 052 */ 053 private static final String LABEL_SEP_REGEX = "[.\u3002\uFF0E\uFF61]"; 054 055 /** 056 * @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035 § 2.3.4.</a< 057 */ 058 static final int MAX_DNSNAME_LENGTH_IN_OCTETS = 255; 059 060 public static final int MAX_LABELS = 128; 061 062 public static final DnsName ROOT = new DnsName("."); 063 064 public static final DnsName IN_ADDR_ARPA = new DnsName("in-addr.arpa"); 065 066 public static final DnsName IP6_ARPA = new DnsName("ip6.arpa"); 067 068 /** 069 * Whether or not the DNS name is validated on construction. 070 */ 071 public static boolean VALIDATE = true; 072 073 /** 074 * The DNS name in ASCII Compatible Encoding (ACE). 075 */ 076 public final String ace; 077 078 /** 079 * The DNS name in raw format, i.e. as it was received from the remote server. This means that compared to 080 * {@link #ace}, this String may not be lower-cased. 081 */ 082 private final String rawAce; 083 084 private transient byte[] bytes; 085 086 private transient byte[] rawBytes; 087 088 private transient String idn; 089 090 private transient String domainpart; 091 092 private transient String hostpart; 093 094 /** 095 * The labels in <b>reverse</b> order. 096 */ 097 private transient DnsLabel[] labels; 098 099 private transient DnsLabel[] rawLabels; 100 101 private transient int hashCode; 102 103 private int size = -1; 104 105 private DnsName(String name) { 106 this(name, true); 107 } 108 109 private DnsName(String name, boolean inAce) { 110 if (name.isEmpty()) { 111 rawAce = ROOT.rawAce; 112 } else { 113 final int nameLength = name.length(); 114 final int nameLastPos = nameLength - 1; 115 116 // Strip potential trailing dot. N.B. that we require nameLength > 2, because we don't want to strip the one 117 // character string containing only a single dot to the empty string. 118 if (nameLength >= 2 && name.charAt(nameLastPos) == '.') { 119 name = name.subSequence(0, nameLastPos).toString(); 120 } 121 122 if (inAce) { 123 // Name is already in ACE format. 124 rawAce = name; 125 } else { 126 rawAce = MiniDnsIdna.toASCII(name); 127 } 128 } 129 130 ace = rawAce.toLowerCase(Locale.US); 131 132 if (!VALIDATE) { 133 return; 134 } 135 136 // Validate the DNS name. 137 validateMaxDnsnameLengthInOctets(); 138 } 139 140 private DnsName(DnsLabel[] rawLabels, boolean validateMaxDnsnameLength) { 141 this.rawLabels = rawLabels; 142 this.labels = new DnsLabel[rawLabels.length]; 143 144 int size = 0; 145 for (int i = 0; i < rawLabels.length; i++) { 146 size += rawLabels[i].length() + 1; 147 labels[i] = rawLabels[i].asLowercaseVariant(); 148 } 149 150 rawAce = labelsToString(rawLabels, size); 151 ace = labelsToString(labels, size); 152 153 if (!validateMaxDnsnameLength || !VALIDATE) { 154 return; 155 } 156 157 validateMaxDnsnameLengthInOctets(); 158 } 159 160 private static String labelsToString(DnsLabel[] labels, int stringLength) { 161 StringBuilder sb = new StringBuilder(stringLength); 162 for (int i = labels.length - 1; i >= 0; i--) { 163 sb.append(labels[i]).append('.'); 164 } 165 sb.setLength(sb.length() - 1); 166 return sb.toString(); 167 } 168 169 private void validateMaxDnsnameLengthInOctets() { 170 setBytesIfRequired(); 171 if (bytes.length > MAX_DNSNAME_LENGTH_IN_OCTETS) { 172 throw new InvalidDnsNameException.DNSNameTooLongException(ace, bytes); 173 } 174 } 175 176 public void writeToStream(OutputStream os) throws IOException { 177 setBytesIfRequired(); 178 os.write(bytes); 179 } 180 181 /** 182 * Serialize a domain name under IDN rules. 183 * 184 * @return The binary domain name representation. 185 */ 186 public byte[] getBytes() { 187 setBytesIfRequired(); 188 return bytes.clone(); 189 } 190 191 public byte[] getRawBytes() { 192 if (rawBytes == null) { 193 setLabelsIfRequired(); 194 rawBytes = toBytes(rawLabels); 195 } 196 197 return rawBytes.clone(); 198 } 199 200 private void setBytesIfRequired() { 201 if (bytes != null) 202 return; 203 204 setLabelsIfRequired(); 205 bytes = toBytes(labels); 206 } 207 208 private static byte[] toBytes(DnsLabel[] labels) { 209 ByteArrayOutputStream baos = new ByteArrayOutputStream(64); 210 for (int i = labels.length - 1; i >= 0; i--) { 211 labels[i].writeToBoas(baos); 212 } 213 214 baos.write(0); 215 216 assert (baos.size() <= MAX_DNSNAME_LENGTH_IN_OCTETS); 217 218 return baos.toByteArray(); 219 } 220 221 private void setLabelsIfRequired() { 222 if (labels != null && rawLabels != null) return; 223 224 if (isRootLabel()) { 225 rawLabels = labels = new DnsLabel[0]; 226 return; 227 } 228 229 labels = getLabels(ace); 230 rawLabels = getLabels(rawAce); 231 } 232 233 private static DnsLabel[] getLabels(String ace) { 234 String[] labels = ace.split(LABEL_SEP_REGEX, MAX_LABELS); 235 236 // Reverse the labels, so that 'foo, example, org' becomes 'org, example, foo'. 237 for (int i = 0; i < labels.length / 2; i++) { 238 String t = labels[i]; 239 int j = labels.length - i - 1; 240 labels[i] = labels[j]; 241 labels[j] = t; 242 } 243 244 try { 245 return DnsLabel.from(labels); 246 } catch (DnsLabel.LabelToLongException e) { 247 throw new InvalidDnsNameException.LabelTooLongException(ace, e.label); 248 } 249 } 250 251 public String getRawAce() { 252 return rawAce; 253 } 254 255 public String asIdn() { 256 if (idn != null) 257 return idn; 258 259 idn = MiniDnsIdna.toUnicode(ace); 260 return idn; 261 } 262 263 /** 264 * Domainpart in ACE representation. 265 * 266 * @return the domainpart in ACE representation. 267 */ 268 public String getDomainpart() { 269 setHostnameAndDomainpartIfRequired(); 270 return domainpart; 271 } 272 273 /** 274 * Hostpart in ACE representation. 275 * 276 * @return the hostpart in ACE representation. 277 */ 278 public String getHostpart() { 279 setHostnameAndDomainpartIfRequired(); 280 return hostpart; 281 } 282 283 private void setHostnameAndDomainpartIfRequired() { 284 if (hostpart != null) return; 285 286 String[] parts = ace.split(LABEL_SEP_REGEX, 2); 287 hostpart = parts[0]; 288 if (parts.length > 1) { 289 domainpart = parts[1]; 290 } else { 291 domainpart = ""; 292 } 293 } 294 295 public int size() { 296 if (size < 0) { 297 if (isRootLabel()) { 298 size = 1; 299 } else { 300 size = ace.length() + 2; 301 } 302 } 303 return size; 304 } 305 306 @Override 307 public int length() { 308 return ace.length(); 309 } 310 311 @Override 312 public char charAt(int index) { 313 return ace.charAt(index); 314 } 315 316 @Override 317 public CharSequence subSequence(int start, int end) { 318 return ace.subSequence(start, end); 319 } 320 321 @Override 322 public String toString() { 323 return ace; 324 } 325 326 public static DnsName from(CharSequence name) { 327 return from(name.toString()); 328 } 329 330 public static DnsName from(String name) { 331 return new DnsName(name, false); 332 } 333 334 /** 335 * Create a DNS name by "concatenating" the child under the parent name. The child can also be seen as the "left" 336 * part of the resulting DNS name and the parent is the "right" part. 337 * <p> 338 * For example using "i.am.the.child" as child and "of.this.parent.example" as parent, will result in a DNS name: 339 * "i.am.the.child.of.this.parent.example". 340 * </p> 341 * 342 * @param child the child DNS name. 343 * @param parent the parent DNS name. 344 * @return the resulting of DNS name. 345 */ 346 public static DnsName from(DnsName child, DnsName parent) { 347 child.setLabelsIfRequired(); 348 parent.setLabelsIfRequired(); 349 350 DnsLabel[] rawLabels = new DnsLabel[child.rawLabels.length + parent.rawLabels.length]; 351 System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length); 352 System.arraycopy(child.rawLabels, 0, rawLabels, parent.rawLabels.length, child.rawLabels.length); 353 return new DnsName(rawLabels, true); 354 } 355 356 public static DnsName from(DnsName... nameComponents) { 357 int labelCount = 0; 358 for (DnsName component : nameComponents) { 359 component.setLabelsIfRequired(); 360 labelCount += component.rawLabels.length; 361 } 362 363 DnsLabel[] rawLabels = new DnsLabel[labelCount]; 364 int destLabelPos = 0; 365 for (int i = nameComponents.length - 1; i >= 0; i--) { 366 DnsName component = nameComponents[i]; 367 System.arraycopy(component.rawLabels, 0, rawLabels, destLabelPos, component.rawLabels.length); 368 destLabelPos += component.rawLabels.length; 369 } 370 371 return new DnsName(rawLabels, true); 372 } 373 374 public static DnsName from(String[] parts) { 375 DnsLabel[] rawLabels = DnsLabel.from(parts); 376 377 return new DnsName(rawLabels, true); 378 } 379 380 /** 381 * Parse a domain name starting at the current offset and moving the input 382 * stream pointer past this domain name (even if cross references occure). 383 * 384 * @param dis The input stream. 385 * @param data The raw data (for cross references). 386 * @return The domain name string. 387 * @throws IOException Should never happen. 388 */ 389 public static DnsName parse(DataInputStream dis, byte data[]) 390 throws IOException { 391 int c = dis.readUnsignedByte(); 392 if ((c & 0xc0) == 0xc0) { 393 c = ((c & 0x3f) << 8) + dis.readUnsignedByte(); 394 HashSet<Integer> jumps = new HashSet<Integer>(); 395 jumps.add(c); 396 return parse(data, c, jumps); 397 } 398 if (c == 0) { 399 return DnsName.ROOT; 400 } 401 byte b[] = new byte[c]; 402 dis.readFully(b); 403 404 String childLabelString = new String(b); 405 DnsName child = new DnsName(childLabelString); 406 407 DnsName parent = parse(dis, data); 408 return DnsName.from(child, parent); 409 } 410 411 /** 412 * Parse a domain name starting at the given offset. 413 * 414 * @param data The raw data. 415 * @param offset The offset. 416 * @param jumps The list of jumps (by now). 417 * @return The parsed domain name. 418 * @throws IllegalStateException on cycles. 419 */ 420 private static DnsName parse(byte data[], int offset, HashSet<Integer> jumps) 421 throws IllegalStateException { 422 int c = data[offset] & 0xff; 423 if ((c & 0xc0) == 0xc0) { 424 c = ((c & 0x3f) << 8) + (data[offset + 1] & 0xff); 425 if (jumps.contains(c)) { 426 throw new IllegalStateException("Cyclic offsets detected."); 427 } 428 jumps.add(c); 429 return parse(data, c, jumps); 430 } 431 if (c == 0) { 432 return DnsName.ROOT; 433 } 434 435 String childLabelString = new String(data, offset + 1, c); 436 DnsName child = new DnsName(childLabelString); 437 438 DnsName parent = parse(data, offset + 1 + c, jumps); 439 return DnsName.from(child, parent); 440 } 441 442 @Override 443 public int compareTo(DnsName other) { 444 return ace.compareTo(other.ace); 445 } 446 447 @Override 448 public boolean equals(Object other) { 449 if (other == null) return false; 450 451 if (other instanceof DnsName) { 452 DnsName otherDnsName = (DnsName) other; 453 setBytesIfRequired(); 454 otherDnsName.setBytesIfRequired(); 455 return Arrays.equals(bytes, otherDnsName.bytes); 456 } 457 458 return false; 459 } 460 461 @Override 462 public int hashCode() { 463 if (hashCode == 0 && !isRootLabel()) { 464 setBytesIfRequired(); 465 hashCode = Arrays.hashCode(bytes); 466 } 467 return hashCode; 468 } 469 470 public boolean isDirectChildOf(DnsName parent) { 471 setLabelsIfRequired(); 472 parent.setLabelsIfRequired(); 473 int parentLabelsCount = parent.labels.length; 474 475 if (labels.length - 1 != parentLabelsCount) 476 return false; 477 478 for (int i = 0; i < parent.labels.length; i++) { 479 if (!labels[i].equals(parent.labels[i])) 480 return false; 481 } 482 483 return true; 484 } 485 486 public boolean isChildOf(DnsName parent) { 487 setLabelsIfRequired(); 488 parent.setLabelsIfRequired(); 489 490 if (labels.length < parent.labels.length) 491 return false; 492 493 for (int i = 0; i < parent.labels.length; i++) { 494 if (!labels[i].equals(parent.labels[i])) 495 return false; 496 } 497 498 return true; 499 } 500 501 public int getLabelCount() { 502 setLabelsIfRequired(); 503 return labels.length; 504 } 505 506 /** 507 * Get a copy of the labels of this DNS name. The resulting array will contain the labels in reverse order, that is, 508 * the top-level domain will be at res[0]. 509 * 510 * @return an array of the labels in reverse order. 511 */ 512 public DnsLabel[] getLabels() { 513 setLabelsIfRequired(); 514 return labels.clone(); 515 } 516 517 /** 518 * Get a copy of the raw labels of this DNS name. The resulting array will contain the labels in reverse order, that is, 519 * the top-level domain will be at res[0]. 520 * 521 * @return an array of the raw labels in reverse order. 522 */ 523 public DnsLabel[] getRawLabels() { 524 setLabelsIfRequired(); 525 return rawLabels.clone(); 526 } 527 528 public DnsName stripToLabels(int labelCount) { 529 setLabelsIfRequired(); 530 531 if (labelCount > labels.length) { 532 throw new IllegalArgumentException(); 533 } 534 535 if (labelCount == labels.length) { 536 return this; 537 } 538 539 if (labelCount == 0) { 540 return ROOT; 541 } 542 543 DnsLabel[] stripedLabels = Arrays.copyOfRange(rawLabels, 0, labelCount); 544 545 return new DnsName(stripedLabels, false); 546 } 547 548 /** 549 * Return the parent of this DNS label. Will return the root label if this label itself is the root label (because there is no parent of root). 550 * <p> 551 * For example: 552 * </p> 553 * <ul> 554 * <li><code>"foo.bar.org".getParent() == "bar.org"</code></li> 555 * <li><code> ".".getParent() == "."</code></li> 556 * </ul> 557 * @return the parent of this DNS label. 558 */ 559 public DnsName getParent() { 560 if (isRootLabel()) return ROOT; 561 return stripToLabels(getLabelCount() - 1); 562 } 563 564 public boolean isRootLabel() { 565 return ace.isEmpty() || ace.equals("."); 566 } 567}