1 // Copyright (c) 1999-2004 Brian Wellington (bwelling (at) xbill.org) 2 3 package org.xbill.DNS; 4 5 import java.io.*; 6 import java.text.*; 7 8 /** 9 * A representation of a domain name. It may either be absolute (fully 10 * qualified) or relative. 11 * 12 * @author Brian Wellington 13 */ 14 15 public class Name implements Comparable, Serializable { 16 17 private static final long serialVersionUID = -7257019940971525644L; 18 19 private static final int LABEL_NORMAL = 0; 20 private static final int LABEL_COMPRESSION = 0xC0; 21 private static final int LABEL_MASK = 0xC0; 22 23 /* The name data */ 24 private byte [] name; 25 26 /* 27 * Effectively an 8 byte array, where the low order byte stores the number 28 * of labels and the 7 higher order bytes store per-label offsets. 29 */ 30 private long offsets; 31 32 /* Precomputed hashcode. */ 33 private int hashcode; 34 35 private static final byte [] emptyLabel = new byte[] {(byte)0}; 36 private static final byte [] wildLabel = new byte[] {(byte)1, (byte)'*'}; 37 38 /** The root name */ 39 public static final Name root; 40 41 /** The root name */ 42 public static final Name empty; 43 44 /** The maximum length of a Name */ 45 private static final int MAXNAME = 255; 46 47 /** The maximum length of a label a Name */ 48 private static final int MAXLABEL = 63; 49 50 /** The maximum number of labels in a Name */ 51 private static final int MAXLABELS = 128; 52 53 /** The maximum number of cached offsets */ 54 private static final int MAXOFFSETS = 7; 55 56 /* Used for printing non-printable characters */ 57 private static final DecimalFormat byteFormat = new DecimalFormat(); 58 59 /* Used to efficiently convert bytes to lowercase */ 60 private static final byte lowercase[] = new byte[256]; 61 62 /* Used in wildcard names. */ 63 private static final Name wild; 64 65 static { 66 byteFormat.setMinimumIntegerDigits(3); 67 for (int i = 0; i < lowercase.length; i++) { 68 if (i < 'A' || i > 'Z') 69 lowercase[i] = (byte)i; 70 else 71 lowercase[i] = (byte)(i - 'A' + 'a'); 72 } 73 root = new Name(); 74 root.appendSafe(emptyLabel, 0, 1); 75 empty = new Name(); 76 empty.name = new byte[0]; 77 wild = new Name(); 78 wild.appendSafe(wildLabel, 0, 1); 79 } 80 81 private 82 Name() { 83 } 84 85 private final void 86 setoffset(int n, int offset) { 87 if (n >= MAXOFFSETS) 88 return; 89 int shift = 8 * (7 - n); 90 offsets &= (~(0xFFL << shift)); 91 offsets |= ((long)offset << shift); 92 } 93 94 private final int 95 offset(int n) { 96 if (n == 0 && getlabels() == 0) 97 return 0; 98 if (n < 0 || n >= getlabels()) 99 throw new IllegalArgumentException("label out of range"); 100 if (n < MAXOFFSETS) { 101 int shift = 8 * (7 - n); 102 return ((int)(offsets >>> shift) & 0xFF); 103 } else { 104 int pos = offset(MAXOFFSETS - 1); 105 for (int i = MAXOFFSETS - 1; i < n; i++) 106 pos += (name[pos] + 1); 107 return (pos); 108 } 109 } 110 111 private final void 112 setlabels(int labels) { 113 offsets &= ~(0xFF); 114 offsets |= labels; 115 } 116 117 private final int 118 getlabels() { 119 return (int)(offsets & 0xFF); 120 } 121 122 private static final void 123 copy(Name src, Name dst) { 124 if (src.offset(0) == 0) { 125 dst.name = src.name; 126 dst.offsets = src.offsets; 127 } else { 128 int offset0 = src.offset(0); 129 int namelen = src.name.length - offset0; 130 int labels = src.labels(); 131 dst.name = new byte[namelen]; 132 System.arraycopy(src.name, offset0, dst.name, 0, namelen); 133 for (int i = 0; i < labels && i < MAXOFFSETS; i++) 134 dst.setoffset(i, src.offset(i) - offset0); 135 dst.setlabels(labels); 136 } 137 } 138 139 private final void 140 append(byte [] array, int start, int n) throws NameTooLongException { 141 int length = (name == null ? 0 : (name.length - offset(0))); 142 int alength = 0; 143 for (int i = 0, pos = start; i < n; i++) { 144 int len = array[pos]; 145 if (len > MAXLABEL) 146 throw new IllegalStateException("invalid label"); 147 len++; 148 pos += len; 149 alength += len; 150 } 151 int newlength = length + alength; 152 if (newlength > MAXNAME) 153 throw new NameTooLongException(); 154 int labels = getlabels(); 155 int newlabels = labels + n; 156 if (newlabels > MAXLABELS) 157 throw new IllegalStateException("too many labels"); 158 byte [] newname = new byte[newlength]; 159 if (length != 0) 160 System.arraycopy(name, offset(0), newname, 0, length); 161 System.arraycopy(array, start, newname, length, alength); 162 name = newname; 163 for (int i = 0, pos = length; i < n; i++) { 164 setoffset(labels + i, pos); 165 pos += (newname[pos] + 1); 166 } 167 setlabels(newlabels); 168 } 169 170 private static TextParseException 171 parseException(String str, String message) { 172 return new TextParseException("'" + str + "': " + message); 173 } 174 175 private final void 176 appendFromString(String fullName, byte [] array, int start, int n) 177 throws TextParseException 178 { 179 try { 180 append(array, start, n); 181 } 182 catch (NameTooLongException e) { 183 throw parseException(fullName, "Name too long"); 184 } 185 } 186 187 private final void 188 appendSafe(byte [] array, int start, int n) { 189 try { 190 append(array, start, n); 191 } 192 catch (NameTooLongException e) { 193 } 194 } 195 196 /** 197 * Create a new name from a string and an origin. This does not automatically 198 * make the name absolute; it will be absolute if it has a trailing dot or an 199 * absolute origin is appended. 200 * @param s The string to be converted 201 * @param origin If the name is not absolute, the origin to be appended. 202 * @throws TextParseException The name is invalid. 203 */ 204 public 205 Name(String s, Name origin) throws TextParseException { 206 if (s.equals("")) 207 throw parseException(s, "empty name"); 208 else if (s.equals("@")) { 209 if (origin == null) 210 copy(empty, this); 211 else 212 copy(origin, this); 213 return; 214 } else if (s.equals(".")) { 215 copy(root, this); 216 return; 217 } 218 int labelstart = -1; 219 int pos = 1; 220 byte [] label = new byte[MAXLABEL + 1]; 221 boolean escaped = false; 222 int digits = 0; 223 int intval = 0; 224 boolean absolute = false; 225 for (int i = 0; i < s.length(); i++) { 226 byte b = (byte) s.charAt(i); 227 if (escaped) { 228 if (b >= '0' && b <= '9' && digits < 3) { 229 digits++; 230 intval *= 10; 231 intval += (b - '0'); 232 if (intval > 255) 233 throw parseException(s, "bad escape"); 234 if (digits < 3) 235 continue; 236 b = (byte) intval; 237 } 238 else if (digits > 0 && digits < 3) 239 throw parseException(s, "bad escape"); 240 if (pos > MAXLABEL) 241 throw parseException(s, "label too long"); 242 labelstart = pos; 243 label[pos++] = b; 244 escaped = false; 245 } else if (b == '\\') { 246 escaped = true; 247 digits = 0; 248 intval = 0; 249 } else if (b == '.') { 250 if (labelstart == -1) 251 throw parseException(s, "invalid empty label"); 252 label[0] = (byte)(pos - 1); 253 appendFromString(s, label, 0, 1); 254 labelstart = -1; 255 pos = 1; 256 } else { 257 if (labelstart == -1) 258 labelstart = i; 259 if (pos > MAXLABEL) 260 throw parseException(s, "label too long"); 261 label[pos++] = b; 262 } 263 } 264 if (digits > 0 && digits < 3) 265 throw parseException(s, "bad escape"); 266 if (escaped) 267 throw parseException(s, "bad escape"); 268 if (labelstart == -1) { 269 appendFromString(s, emptyLabel, 0, 1); 270 absolute = true; 271 } else { 272 label[0] = (byte)(pos - 1); 273 appendFromString(s, label, 0, 1); 274 } 275 if (origin != null && !absolute) 276 appendFromString(s, origin.name, 0, origin.getlabels()); 277 } 278 279 /** 280 * Create a new name from a string. This does not automatically make the name 281 * absolute; it will be absolute if it has a trailing dot. 282 * @param s The string to be converted 283 * @throws TextParseException The name is invalid. 284 */ 285 public 286 Name(String s) throws TextParseException { 287 this(s, null); 288 } 289 290 /** 291 * Create a new name from a string and an origin. This does not automatically 292 * make the name absolute; it will be absolute if it has a trailing dot or an 293 * absolute origin is appended. This is identical to the constructor, except 294 * that it will avoid creating new objects in some cases. 295 * @param s The string to be converted 296 * @param origin If the name is not absolute, the origin to be appended. 297 * @throws TextParseException The name is invalid. 298 */ 299 public static Name 300 fromString(String s, Name origin) throws TextParseException { 301 if (s.equals("@") && origin != null) 302 return origin; 303 else if (s.equals(".")) 304 return (root); 305 306 return new Name(s, origin); 307 } 308 309 /** 310 * Create a new name from a string. This does not automatically make the name 311 * absolute; it will be absolute if it has a trailing dot. This is identical 312 * to the constructor, except that it will avoid creating new objects in some 313 * cases. 314 * @param s The string to be converted 315 * @throws TextParseException The name is invalid. 316 */ 317 public static Name 318 fromString(String s) throws TextParseException { 319 return fromString(s, null); 320 } 321 322 /** 323 * Create a new name from a constant string. This should only be used when 324 the name is known to be good - that is, when it is constant. 325 * @param s The string to be converted 326 * @throws IllegalArgumentException The name is invalid. 327 */ 328 public static Name 329 fromConstantString(String s) { 330 try { 331 return fromString(s, null); 332 } 333 catch (TextParseException e) { 334 throw new IllegalArgumentException("Invalid name '" + s + "'"); 335 } 336 } 337 338 /** 339 * Create a new name from DNS a wire format message 340 * @param in A stream containing the DNS message which is currently 341 * positioned at the start of the name to be read. 342 */ 343 public 344 Name(DNSInput in) throws WireParseException { 345 int len, pos; 346 boolean done = false; 347 byte [] label = new byte[MAXLABEL + 1]; 348 boolean savedState = false; 349 350 while (!done) { 351 len = in.readU8(); 352 switch (len & LABEL_MASK) { 353 case LABEL_NORMAL: 354 if (getlabels() >= MAXLABELS) 355 throw new WireParseException("too many labels"); 356 if (len == 0) { 357 append(emptyLabel, 0, 1); 358 done = true; 359 } else { 360 label[0] = (byte)len; 361 in.readByteArray(label, 1, len); 362 append(label, 0, 1); 363 } 364 break; 365 case LABEL_COMPRESSION: 366 pos = in.readU8(); 367 pos += ((len & ~LABEL_MASK) << 8); 368 if (Options.check("verbosecompression")) 369 System.err.println("currently " + in.current() + 370 ", pointer to " + pos); 371 372 if (pos >= in.current() - 2) 373 throw new WireParseException("bad compression"); 374 if (!savedState) { 375 in.save(); 376 savedState = true; 377 } 378 in.jump(pos); 379 if (Options.check("verbosecompression")) 380 System.err.println("current name '" + this + 381 "', seeking to " + pos); 382 break; 383 default: 384 throw new WireParseException("bad label type"); 385 } 386 } 387 if (savedState) { 388 in.restore(); 389 } 390 } 391 392 /** 393 * Create a new name from DNS wire format 394 * @param b A byte array containing the wire format of the name. 395 */ 396 public 397 Name(byte [] b) throws IOException { 398 this(new DNSInput(b)); 399 } 400 401 /** 402 * Create a new name by removing labels from the beginning of an existing Name 403 * @param src An existing Name 404 * @param n The number of labels to remove from the beginning in the copy 405 */ 406 public 407 Name(Name src, int n) { 408 int slabels = src.labels(); 409 if (n > slabels) 410 throw new IllegalArgumentException("attempted to remove too " + 411 "many labels"); 412 name = src.name; 413 setlabels(slabels - n); 414 for (int i = 0; i < MAXOFFSETS && i < slabels - n; i++) 415 setoffset(i, src.offset(i + n)); 416 } 417 418 /** 419 * Creates a new name by concatenating two existing names. 420 * @param prefix The prefix name. 421 * @param suffix The suffix name. 422 * @return The concatenated name. 423 * @throws NameTooLongException The name is too long. 424 */ 425 public static Name 426 concatenate(Name prefix, Name suffix) throws NameTooLongException { 427 if (prefix.isAbsolute()) 428 return (prefix); 429 Name newname = new Name(); 430 copy(prefix, newname); 431 newname.append(suffix.name, suffix.offset(0), suffix.getlabels()); 432 return newname; 433 } 434 435 /** 436 * If this name is a subdomain of origin, return a new name relative to 437 * origin with the same value. Otherwise, return the existing name. 438 * @param origin The origin to remove. 439 * @return The possibly relativized name. 440 */ 441 public Name 442 relativize(Name origin) { 443 if (origin == null || !subdomain(origin)) 444 return this; 445 Name newname = new Name(); 446 copy(this, newname); 447 int length = length() - origin.length(); 448 int labels = newname.labels() - origin.labels(); 449 newname.setlabels(labels); 450 newname.name = new byte[length]; 451 System.arraycopy(name, offset(0), newname.name, 0, length); 452 return newname; 453 } 454 455 /** 456 * Generates a new Name with the first n labels replaced by a wildcard 457 * @return The wildcard name 458 */ 459 public Name 460 wild(int n) { 461 if (n < 1) 462 throw new IllegalArgumentException("must replace 1 or more " + 463 "labels"); 464 try { 465 Name newname = new Name(); 466 copy(wild, newname); 467 newname.append(name, offset(n), getlabels() - n); 468 return newname; 469 } 470 catch (NameTooLongException e) { 471 throw new IllegalStateException 472 ("Name.wild: concatenate failed"); 473 } 474 } 475 476 /** 477 * Generates a new Name to be used when following a DNAME. 478 * @param dname The DNAME record to follow. 479 * @return The constructed name. 480 * @throws NameTooLongException The resulting name is too long. 481 */ 482 public Name 483 fromDNAME(DNAMERecord dname) throws NameTooLongException { 484 Name dnameowner = dname.getName(); 485 Name dnametarget = dname.getTarget(); 486 if (!subdomain(dnameowner)) 487 return null; 488 489 int plabels = labels() - dnameowner.labels(); 490 int plength = length() - dnameowner.length(); 491 int pstart = offset(0); 492 493 int dlabels = dnametarget.labels(); 494 int dlength = dnametarget.length(); 495 496 if (plength + dlength > MAXNAME) 497 throw new NameTooLongException(); 498 499 Name newname = new Name(); 500 newname.setlabels(plabels + dlabels); 501 newname.name = new byte[plength + dlength]; 502 System.arraycopy(name, pstart, newname.name, 0, plength); 503 System.arraycopy(dnametarget.name, 0, newname.name, plength, dlength); 504 505 for (int i = 0, pos = 0; i < MAXOFFSETS && i < plabels + dlabels; i++) { 506 newname.setoffset(i, pos); 507 pos += (newname.name[pos] + 1); 508 } 509 return newname; 510 } 511 512 /** 513 * Is this name a wildcard? 514 */ 515 public boolean 516 isWild() { 517 if (labels() == 0) 518 return false; 519 return (name[0] == (byte)1 && name[1] == (byte)'*'); 520 } 521 522 /** 523 * Is this name absolute? 524 */ 525 public boolean 526 isAbsolute() { 527 if (labels() == 0) 528 return false; 529 return (name[name.length - 1] == 0); 530 } 531 532 /** 533 * The length of the name. 534 */ 535 public short 536 length() { 537 if (getlabels() == 0) 538 return 0; 539 return (short)(name.length - offset(0)); 540 } 541 542 /** 543 * The number of labels in the name. 544 */ 545 public int 546 labels() { 547 return getlabels(); 548 } 549 550 /** 551 * Is the current Name a subdomain of the specified name? 552 */ 553 public boolean 554 subdomain(Name domain) { 555 int labels = labels(); 556 int dlabels = domain.labels(); 557 if (dlabels > labels) 558 return false; 559 if (dlabels == labels) 560 return equals(domain); 561 return domain.equals(name, offset(labels - dlabels)); 562 } 563 564 private String 565 byteString(byte [] array, int pos) { 566 StringBuffer sb = new StringBuffer(); 567 int len = array[pos++]; 568 for (int i = pos; i < pos + len; i++) { 569 int b = array[i] & 0xFF; 570 if (b <= 0x20 || b >= 0x7f) { 571 sb.append('\\'); 572 sb.append(byteFormat.format(b)); 573 } 574 else if (b == '"' || b == '(' || b == ')' || b == '.' || 575 b == ';' || b == '\\' || b == '@' || b == '$') 576 { 577 sb.append('\\'); 578 sb.append((char)b); 579 } 580 else 581 sb.append((char)b); 582 } 583 return sb.toString(); 584 } 585 586 /** 587 * Convert a Name to a String 588 * @return The representation of this name as a (printable) String. 589 */ 590 public String 591 toString() { 592 int labels = labels(); 593 if (labels == 0) 594 return "@"; 595 else if (labels == 1 && name[offset(0)] == 0) 596 return "."; 597 StringBuffer sb = new StringBuffer(); 598 for (int i = 0, pos = offset(0); i < labels; i++) { 599 int len = name[pos]; 600 if (len > MAXLABEL) 601 throw new IllegalStateException("invalid label"); 602 if (len == 0) 603 break; 604 sb.append(byteString(name, pos)); 605 sb.append('.'); 606 pos += (1 + len); 607 } 608 if (!isAbsolute()) 609 sb.deleteCharAt(sb.length() - 1); 610 return sb.toString(); 611 } 612 613 /** 614 * Retrieve the nth label of a Name. This makes a copy of the label; changing 615 * this does not change the Name. 616 * @param n The label to be retrieved. The first label is 0. 617 */ 618 public byte [] 619 getLabel(int n) { 620 int pos = offset(n); 621 byte len = (byte)(name[pos] + 1); 622 byte [] label = new byte[len]; 623 System.arraycopy(name, pos, label, 0, len); 624 return label; 625 } 626 627 /** 628 * Convert the nth label in a Name to a String 629 * @param n The label to be converted to a (printable) String. The first 630 * label is 0. 631 */ 632 public String 633 getLabelString(int n) { 634 int pos = offset(n); 635 return byteString(name, pos); 636 } 637 638 /** 639 * Emit a Name in DNS wire format 640 * @param out The output stream containing the DNS message. 641 * @param c The compression context, or null of no compression is desired. 642 * @throws IllegalArgumentException The name is not absolute. 643 */ 644 public void 645 toWire(DNSOutput out, Compression c) { 646 if (!isAbsolute()) 647 throw new IllegalArgumentException("toWire() called on " + 648 "non-absolute name"); 649 650 int labels = labels(); 651 for (int i = 0; i < labels - 1; i++) { 652 Name tname; 653 if (i == 0) 654 tname = this; 655 else 656 tname = new Name(this, i); 657 int pos = -1; 658 if (c != null) 659 pos = c.get(tname); 660 if (pos >= 0) { 661 pos |= (LABEL_MASK << 8); 662 out.writeU16(pos); 663 return; 664 } else { 665 if (c != null) 666 c.add(out.current(), tname); 667 int off = offset(i); 668 out.writeByteArray(name, off, name[off] + 1); 669 } 670 } 671 out.writeU8(0); 672 } 673 674 /** 675 * Emit a Name in DNS wire format 676 * @throws IllegalArgumentException The name is not absolute. 677 */ 678 public byte [] 679 toWire() { 680 DNSOutput out = new DNSOutput(); 681 toWire(out, null); 682 return out.toByteArray(); 683 } 684 685 /** 686 * Emit a Name in canonical DNS wire format (all lowercase) 687 * @param out The output stream to which the message is written. 688 */ 689 public void 690 toWireCanonical(DNSOutput out) { 691 byte [] b = toWireCanonical(); 692 out.writeByteArray(b); 693 } 694 695 /** 696 * Emit a Name in canonical DNS wire format (all lowercase) 697 * @return The canonical form of the name. 698 */ 699 public byte [] 700 toWireCanonical() { 701 int labels = labels(); 702 if (labels == 0) 703 return (new byte[0]); 704 byte [] b = new byte[name.length - offset(0)]; 705 for (int i = 0, spos = offset(0), dpos = 0; i < labels; i++) { 706 int len = name[spos]; 707 if (len > MAXLABEL) 708 throw new IllegalStateException("invalid label"); 709 b[dpos++] = name[spos++]; 710 for (int j = 0; j < len; j++) 711 b[dpos++] = lowercase[(name[spos++] & 0xFF)]; 712 } 713 return b; 714 } 715 716 /** 717 * Emit a Name in DNS wire format 718 * @param out The output stream containing the DNS message. 719 * @param c The compression context, or null of no compression is desired. 720 * @param canonical If true, emit the name in canonicalized form 721 * (all lowercase). 722 * @throws IllegalArgumentException The name is not absolute. 723 */ 724 public void 725 toWire(DNSOutput out, Compression c, boolean canonical) { 726 if (canonical) 727 toWireCanonical(out); 728 else 729 toWire(out, c); 730 } 731 732 private final boolean 733 equals(byte [] b, int bpos) { 734 int labels = labels(); 735 for (int i = 0, pos = offset(0); i < labels; i++) { 736 if (name[pos] != b[bpos]) 737 return false; 738 int len = name[pos++]; 739 bpos++; 740 if (len > MAXLABEL) 741 throw new IllegalStateException("invalid label"); 742 for (int j = 0; j < len; j++) 743 if (lowercase[(name[pos++] & 0xFF)] != 744 lowercase[(b[bpos++] & 0xFF)]) 745 return false; 746 } 747 return true; 748 } 749 750 /** 751 * Are these two Names equivalent? 752 */ 753 public boolean 754 equals(Object arg) { 755 if (arg == this) 756 return true; 757 if (arg == null || !(arg instanceof Name)) 758 return false; 759 Name d = (Name) arg; 760 if (d.hashcode == 0) 761 d.hashCode(); 762 if (hashcode == 0) 763 hashCode(); 764 if (d.hashcode != hashcode) 765 return false; 766 if (d.labels() != labels()) 767 return false; 768 return equals(d.name, d.offset(0)); 769 } 770 771 /** 772 * Computes a hashcode based on the value 773 */ 774 public int 775 hashCode() { 776 if (hashcode != 0) 777 return (hashcode); 778 int code = 0; 779 for (int i = offset(0); i < name.length; i++) 780 code += ((code << 3) + lowercase[(name[i] & 0xFF)]); 781 hashcode = code; 782 return hashcode; 783 } 784 785 /** 786 * Compares this Name to another Object. 787 * @param o The Object to be compared. 788 * @return The value 0 if the argument is a name equivalent to this name; 789 * a value less than 0 if the argument is less than this name in the canonical 790 * ordering, and a value greater than 0 if the argument is greater than this 791 * name in the canonical ordering. 792 * @throws ClassCastException if the argument is not a Name. 793 */ 794 public int 795 compareTo(Object o) { 796 Name arg = (Name) o; 797 798 if (this == arg) 799 return (0); 800 801 int labels = labels(); 802 int alabels = arg.labels(); 803 int compares = labels > alabels ? alabels : labels; 804 805 for (int i = 1; i <= compares; i++) { 806 int start = offset(labels - i); 807 int astart = arg.offset(alabels - i); 808 int length = name[start]; 809 int alength = arg.name[astart]; 810 for (int j = 0; j < length && j < alength; j++) { 811 int n = lowercase[(name[j + start + 1]) & 0xFF] - 812 lowercase[(arg.name[j + astart + 1]) & 0xFF]; 813 if (n != 0) 814 return (n); 815 } 816 if (length != alength) 817 return (length - alength); 818 } 819 return (labels - alabels); 820 } 821 822 } 823