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