1 /*
   2  * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.dns;
  27 
  28 
  29 import java.util.ArrayList;
  30 import java.util.Comparator;
  31 import java.util.Enumeration;
  32 
  33 import javax.naming.*;
  34 
  35 
  36 /**
  37  * <tt>DnsName</tt> implements compound names for DNS as specified by
  38  * RFCs 1034 and 1035, and as updated and clarified by RFCs 1123 and 2181.
  39  *
  40  * <p> The labels in a domain name correspond to JNDI atomic names.
  41  * Each label must be less than 64 octets in length, and only the
  42  * optional root label at the end of the name may be 0 octets long.
  43  * The sum of the lengths of all labels in a name, plus the number of
  44  * non-root labels plus 1, must be less than 256.  The textual
  45  * representation of a domain name consists of the labels, escaped as
  46  * needed, dot-separated, and ordered right-to-left.
  47  *
  48  * <p> A label consists of a sequence of octets, each of which may
  49  * have any value from 0 to 255.
  50  *
  51  * <p> <em>Host names</em> are a subset of domain names.
  52  * Their labels contain only ASCII letters, digits, and hyphens, and
  53  * none may begin or end with a hyphen.  While names not conforming to
  54  * these rules may be valid domain names, they will not be usable by a
  55  * number of DNS applications, and should in most cases be avoided.
  56  *
  57  * <p> DNS does not specify an encoding (such as UTF-8) to use for
  58  * octets with non-ASCII values.  As of this writing there is some
  59  * work going on in this area, but it is not yet finalized.
  60  * <tt>DnsName</tt> currently converts any non-ASCII octets into
  61  * characters using ISO-LATIN-1 encoding, in effect taking the
  62  * value of each octet and storing it directly into the low-order byte
  63  * of a Java character and <i>vice versa</i>.  As a consequence, no
  64  * character in a DNS name will ever have a non-zero high-order byte.
  65  * When the work on internationalizing domain names has stabilized
  66  * (see for example <i>draft-ietf-idn-idna-10.txt</i>), <tt>DnsName</tt>
  67  * may be updated to conform to that work.
  68  *
  69  * <p> Backslash (<tt>\</tt>) is used as the escape character in the
  70  * textual representation of a domain name.  The character sequence
  71  * `<tt>\DDD</tt>', where <tt>DDD</tt> is a 3-digit decimal number
  72  * (with leading zeros if needed), represents the octet whose value
  73  * is <tt>DDD</tt>.  The character sequence `<tt>\C</tt>', where
  74  * <tt>C</tt> is a character other than <tt>'0'</tt> through
  75  * <tt>'9'</tt>, represents the octet whose value is that of
  76  * <tt>C</tt> (again using ISO-LATIN-1 encoding); this is particularly
  77  * useful for escaping <tt>'.'</tt> or backslash itself.  Backslash is
  78  * otherwise not allowed in a domain name.  Note that escape characters
  79  * are interpreted when a name is parsed.  So, for example, the character
  80  * sequences `<tt>S</tt>', `<tt>\S</tt>', and `<tt>\083</tt>' each
  81  * represent the same one-octet name.  The <tt>toString()</tt> method
  82  * does not generally insert escape sequences except where necessary.
  83  * If, however, the <tt>DnsName</tt> was constructed using unneeded
  84  * escapes, those escapes may appear in the <tt>toString</tt> result.
  85  *
  86  * <p> Atomic names passed as parameters to methods of
  87  * <tt>DnsName</tt>, and those returned by them, are unescaped.  So,
  88  * for example, <tt>(new&nbsp;DnsName()).add("a.b")</tt> creates an
  89  * object representing the one-label domain name <tt>a\.b</tt>, and
  90  * calling <tt>get(0)</tt> on this object returns <tt>"a.b"</tt>.
  91  *
  92  * <p> While DNS names are case-preserving, comparisons between them
  93  * are case-insensitive.  When comparing names containing non-ASCII
  94  * octets, <tt>DnsName</tt> uses case-insensitive comparison
  95  * between pairs of ASCII values, and exact binary comparison
  96  * otherwise.
  97 
  98  * <p> A <tt>DnsName</tt> instance is not synchronized against
  99  * concurrent access by multiple threads.
 100  *
 101  * @author Scott Seligman
 102  */
 103 
 104 
 105 public final class DnsName implements Name {
 106 
 107     // If non-null, the domain name represented by this DnsName.
 108     private String domain = "";
 109 
 110     // The labels of this domain name, as a list of strings.  Index 0
 111     // corresponds to the leftmost (least significant) label:  note that
 112     // this is the reverse of the ordering used by the Name interface.
 113     private ArrayList<String> labels = new ArrayList<>();
 114 
 115     // The number of octets needed to carry this domain name in a DNS
 116     // packet.  Equal to the sum of the lengths of each label, plus the
 117     // number of non-root labels, plus 1.  Must remain less than 256.
 118     private short octets = 1;
 119 
 120 
 121     /**
 122      * Constructs a <tt>DnsName</tt> representing the empty domain name.
 123      */
 124     public DnsName() {
 125     }
 126 
 127     /**
 128      * Constructs a <tt>DnsName</tt> representing a given domain name.
 129      *
 130      * @param   name    the domain name to parse
 131      * @throws InvalidNameException if <tt>name</tt> does not conform
 132      *          to DNS syntax.
 133      */
 134     public DnsName(String name) throws InvalidNameException {
 135         parse(name);
 136     }
 137 
 138     /*
 139      * Returns a new DnsName with its name components initialized to
 140      * the components of "n" in the range [beg,end).  Indexing is as
 141      * for the Name interface, with 0 being the most significant.
 142      */
 143     private DnsName(DnsName n, int beg, int end) {
 144         // Compute indexes into "labels", which has least-significant label
 145         // at index 0 (opposite to the convention used for "beg" and "end").
 146         int b = n.size() - end;
 147         int e = n.size() - beg;
 148         labels.addAll(n.labels.subList(b, e));
 149 
 150         if (size() == n.size()) {
 151             domain = n.domain;
 152             octets = n.octets;
 153         } else {
 154             for (String label: labels) {
 155                 if (label.length() > 0) {
 156                     octets += (short) (label.length() + 1);
 157                 }
 158             }
 159         }
 160     }
 161 
 162 
 163     public String toString() {
 164         if (domain == null) {
 165             StringBuilder buf = new StringBuilder();
 166             for (String label: labels) {
 167                 if (buf.length() > 0 || label.length() == 0) {
 168                     buf.append('.');
 169                 }
 170                 escape(buf, label);
 171             }
 172             domain = buf.toString();
 173         }
 174         return domain;
 175     }
 176 
 177     /**
 178      * Does this domain name follow <em>host name</em> syntax?
 179      */
 180     public boolean isHostName() {
 181         for (String label: labels) {
 182             if (!isHostNameLabel(label)) {
 183                 return false;
 184             }
 185         }
 186         return true;
 187     }
 188 
 189     public short getOctets() {
 190         return octets;
 191     }
 192 
 193     public int size() {
 194         return labels.size();
 195     }
 196 
 197     public boolean isEmpty() {
 198         return (size() == 0);
 199     }
 200 
 201     public int hashCode() {
 202         int h = 0;
 203         for (int i = 0; i < size(); i++) {
 204             h = 31 * h + getKey(i).hashCode();
 205         }
 206         return h;
 207     }
 208 
 209     public boolean equals(Object obj) {
 210         if (!(obj instanceof Name) || (obj instanceof CompositeName)) {
 211             return false;
 212         }
 213         Name n = (Name) obj;
 214         return ((size() == n.size()) &&         // shortcut:  do sizes differ?
 215                 (compareTo(obj) == 0));
 216     }
 217 
 218     public int compareTo(Object obj) {
 219         Name n = (Name) obj;
 220         return compareRange(0, size(), n);      // never 0 if sizes differ
 221     }
 222 
 223     public boolean startsWith(Name n) {
 224         return ((size() >= n.size()) &&
 225                 (compareRange(0, n.size(), n) == 0));
 226     }
 227 
 228     public boolean endsWith(Name n) {
 229         return ((size() >= n.size()) &&
 230                 (compareRange(size() - n.size(), size(), n) == 0));
 231     }
 232 
 233     public String get(int pos) {
 234         if (pos < 0 || pos >= size()) {
 235             throw new ArrayIndexOutOfBoundsException();
 236         }
 237         int i = size() - pos - 1;       // index of "pos" component in "labels"
 238         return labels.get(i);
 239     }
 240 
 241     public Enumeration<String> getAll() {
 242         return new Enumeration<String>() {
 243             int pos = 0;
 244             public boolean hasMoreElements() {
 245                 return (pos < size());
 246             }
 247             public String nextElement() {
 248                 if (pos < size()) {
 249                     return get(pos++);
 250                 }
 251                 throw new java.util.NoSuchElementException();
 252             }
 253         };
 254     }
 255 
 256     public Name getPrefix(int pos) {
 257         return new DnsName(this, 0, pos);
 258     }
 259 
 260     public Name getSuffix(int pos) {
 261         return new DnsName(this, pos, size());
 262     }
 263 
 264     public Object clone() {
 265         return new DnsName(this, 0, size());
 266     }
 267 
 268     public Object remove(int pos) {
 269         if (pos < 0 || pos >= size()) {
 270             throw new ArrayIndexOutOfBoundsException();
 271         }
 272         int i = size() - pos - 1;     // index of element to remove in "labels"
 273         String label = labels.remove(i);
 274         int len = label.length();
 275         if (len > 0) {
 276             octets -= (short) (len + 1);
 277         }
 278         domain = null;          // invalidate "domain"
 279         return label;
 280     }
 281 
 282     public Name add(String comp) throws InvalidNameException {
 283         return add(size(), comp);
 284     }
 285 
 286     public Name add(int pos, String comp) throws InvalidNameException {
 287         if (pos < 0 || pos > size()) {
 288             throw new ArrayIndexOutOfBoundsException();
 289         }
 290         // Check for empty labels:  may have only one, and only at end.
 291         int len = comp.length();
 292         if ((pos > 0 && len == 0) ||
 293             (pos == 0 && hasRootLabel())) {
 294                 throw new InvalidNameException(
 295                         "Empty label must be the last label in a domain name");
 296         }
 297         // Check total name length.
 298         if (len > 0) {
 299             if (octets + len + 1 >= 256) {
 300                 throw new InvalidNameException("Name too long");
 301             }
 302             octets += (short) (len + 1);
 303         }
 304 
 305         int i = size() - pos;   // index for insertion into "labels"
 306         verifyLabel(comp);
 307         labels.add(i, comp);
 308 
 309         domain = null;          // invalidate "domain"
 310         return this;
 311     }
 312 
 313     public Name addAll(Name suffix) throws InvalidNameException {
 314         return addAll(size(), suffix);
 315     }
 316 
 317     public Name addAll(int pos, Name n) throws InvalidNameException {
 318         if (n instanceof DnsName) {
 319             // "n" is a DnsName so we can insert it as a whole, rather than
 320             // verifying and inserting it component-by-component.
 321             // More code, but less work.
 322             DnsName dn = (DnsName) n;
 323 
 324             if (dn.isEmpty()) {
 325                 return this;
 326             }
 327             // Check for empty labels:  may have only one, and only at end.
 328             if ((pos > 0 && dn.hasRootLabel()) ||
 329                 (pos == 0 && hasRootLabel())) {
 330                     throw new InvalidNameException(
 331                         "Empty label must be the last label in a domain name");
 332             }
 333 
 334             short newOctets = (short) (octets + dn.octets - 1);
 335             if (newOctets > 255) {
 336                 throw new InvalidNameException("Name too long");
 337             }
 338             octets = newOctets;
 339             int i = size() - pos;       // index for insertion into "labels"
 340             labels.addAll(i, dn.labels);
 341 
 342             // Preserve "domain" if we're appending or prepending,
 343             // otherwise invalidate it.
 344             if (isEmpty()) {
 345                 domain = dn.domain;
 346             } else if (domain == null || dn.domain == null) {
 347                 domain = null;
 348             } else if (pos == 0) {
 349                 domain += (dn.domain.equals(".") ? "" : ".") + dn.domain;
 350             } else if (pos == size()) {
 351                 domain = dn.domain + (domain.equals(".") ? "" : ".") + domain;
 352             } else {
 353                 domain = null;
 354             }
 355 
 356         } else if (n instanceof CompositeName) {
 357             n = (DnsName) n;            // force ClassCastException
 358 
 359         } else {                // "n" is a compound name, but not a DnsName.
 360             // Add labels least-significant first:  sometimes more efficient.
 361             for (int i = n.size() - 1; i >= 0; i--) {
 362                 add(pos, n.get(i));
 363             }
 364         }
 365         return this;
 366     }
 367 
 368 
 369     boolean hasRootLabel() {
 370         return (!isEmpty() &&
 371                 get(0).equals(""));
 372     }
 373 
 374     /*
 375      * Helper method for public comparison methods.  Lexicographically
 376      * compares components of this name in the range [beg,end) with
 377      * all components of "n".  Indexing is as for the Name interface,
 378      * with 0 being the most significant.  Returns negative, zero, or
 379      * positive as these name components are less than, equal to, or
 380      * greater than those of "n".
 381      */
 382     private int compareRange(int beg, int end, Name n) {
 383         if (n instanceof CompositeName) {
 384             n = (DnsName) n;                    // force ClassCastException
 385         }
 386         // Loop through labels, starting with most significant.
 387         int minSize = Math.min(end - beg, n.size());
 388         for (int i = 0; i < minSize; i++) {
 389             String label1 = get(i + beg);
 390             String label2 = n.get(i);
 391 
 392             int j = size() - (i + beg) - 1;     // index of label1 in "labels"
 393             // assert (label1 == labels.get(j));
 394 
 395             int c = compareLabels(label1, label2);
 396             if (c != 0) {
 397                 return c;
 398             }
 399         }
 400         return ((end - beg) - n.size());        // longer range wins
 401     }
 402 
 403     /*
 404      * Returns a key suitable for hashing the label at index i.
 405      * Indexing is as for the Name interface, with 0 being the most
 406      * significant.
 407      */
 408     String getKey(int i) {
 409         return keyForLabel(get(i));
 410     }
 411 
 412 
 413     /*
 414      * Parses a domain name, setting the values of instance vars accordingly.
 415      */
 416     private void parse(String name) throws InvalidNameException {
 417 
 418         StringBuilder label = new StringBuilder();      // label being parsed
 419 
 420         for (int i = 0; i < name.length(); i++) {
 421             char c = name.charAt(i);
 422 
 423             if (c == '\\') {                    // found an escape sequence
 424                 c = getEscapedOctet(name, i++);
 425                 if (isDigit(name.charAt(i))) {  // sequence is \DDD
 426                     i += 2;                     // consume remaining digits
 427                 }
 428                 label.append(c);
 429 
 430             } else if (c != '.') {              // an unescaped octet
 431                 label.append(c);
 432 
 433             } else {                            // found '.' separator
 434                 add(0, label.toString());       // check syntax, then add label
 435                                                 //   to end of name
 436                 label.delete(0, i);             // clear buffer for next label
 437             }
 438         }
 439 
 440         // If name is neither "." nor "", the octets (zero or more)
 441         // from the rightmost dot onward are now added as the final
 442         // label of the name.  Those two are special cases in that for
 443         // all other domain names, the number of labels is one greater
 444         // than the number of dot separators.
 445         if (!name.equals("") && !name.equals(".")) {
 446             add(0, label.toString());
 447         }
 448 
 449         domain = name;          // do this last, since add() sets it to null
 450     }
 451 
 452     /*
 453      * Returns (as a char) the octet indicated by the escape sequence
 454      * at a given position within a domain name.
 455      * @throws InvalidNameException if a valid escape sequence is not found.
 456      */
 457     private static char getEscapedOctet(String name, int pos)
 458                                                 throws InvalidNameException {
 459         try {
 460             // assert (name.charAt(pos) == '\\');
 461             char c1 = name.charAt(++pos);
 462             if (isDigit(c1)) {          // sequence is `\DDD'
 463                 char c2 = name.charAt(++pos);
 464                 char c3 = name.charAt(++pos);
 465                 if (isDigit(c2) && isDigit(c3)) {
 466                     return (char)
 467                         ((c1 - '0') * 100 + (c2 - '0') * 10 + (c3 - '0'));
 468                 } else {
 469                     throw new InvalidNameException(
 470                             "Invalid escape sequence in " + name);
 471                 }
 472             } else {                    // sequence is `\C'
 473                 return c1;
 474             }
 475         } catch (IndexOutOfBoundsException e) {
 476             throw new InvalidNameException(
 477                     "Invalid escape sequence in " + name);
 478         }
 479     }
 480 
 481     /*
 482      * Checks that this label is valid.
 483      * @throws InvalidNameException if label is not valid.
 484      */
 485     private static void verifyLabel(String label) throws InvalidNameException {
 486         if (label.length() > 63) {
 487             throw new InvalidNameException(
 488                     "Label exceeds 63 octets: " + label);
 489         }
 490         // Check for two-byte characters.
 491         for (int i = 0; i < label.length(); i++) {
 492             char c = label.charAt(i);
 493             if ((c & 0xFF00) != 0) {
 494                 throw new InvalidNameException(
 495                         "Label has two-byte char: " + label);
 496             }
 497         }
 498     }
 499 
 500     /*
 501      * Does this label conform to host name syntax?
 502      */
 503     private static boolean isHostNameLabel(String label) {
 504         for (int i = 0; i < label.length(); i++) {
 505             char c = label.charAt(i);
 506             if (!isHostNameChar(c)) {
 507                 return false;
 508             }
 509         }
 510         return !(label.startsWith("-") || label.endsWith("-"));
 511     }
 512 
 513     private static boolean isHostNameChar(char c) {
 514         return (c == '-' ||
 515                 c >= 'a' && c <= 'z' ||
 516                 c >= 'A' && c <= 'Z' ||
 517                 c >= '0' && c <= '9');
 518     }
 519 
 520     private static boolean isDigit(char c) {
 521         return (c >= '0' && c <= '9');
 522     }
 523 
 524     /*
 525      * Append a label to buf, escaping as needed.
 526      */
 527     private static void escape(StringBuilder buf, String label) {
 528         for (int i = 0; i < label.length(); i++) {
 529             char c = label.charAt(i);
 530             if (c == '.' || c == '\\') {
 531                 buf.append('\\');
 532             }
 533             buf.append(c);
 534         }
 535     }
 536 
 537     /*
 538      * Compares two labels, ignoring case for ASCII values.
 539      * Returns negative, zero, or positive as the first label
 540      * is less than, equal to, or greater than the second.
 541      * See keyForLabel().
 542      */
 543     private static int compareLabels(String label1, String label2) {
 544         int min = Math.min(label1.length(), label2.length());
 545         for (int i = 0; i < min; i++) {
 546             char c1 = label1.charAt(i);
 547             char c2 = label2.charAt(i);
 548             if (c1 >= 'A' && c1 <= 'Z') {
 549                 c1 += 'a' - 'A';                        // to lower case
 550             }
 551             if (c2 >= 'A' && c2 <= 'Z') {
 552                 c2 += 'a' - 'A';                        // to lower case
 553             }
 554             if (c1 != c2) {
 555                 return (c1 - c2);
 556             }
 557         }
 558         return (label1.length() - label2.length());     // the longer one wins
 559     }
 560 
 561     /*
 562      * Returns a key suitable for hashing a label.  Two labels map to
 563      * the same key iff they are equal, taking possible case-folding
 564      * into account.  See compareLabels().
 565      */
 566     private static String keyForLabel(String label) {
 567         StringBuilder sb = new StringBuilder(label.length());
 568         for (int i = 0; i < label.length(); i++) {
 569             char c = label.charAt(i);
 570             if (c >= 'A' && c <= 'Z') {
 571                 c += 'a' - 'A';                         // to lower case
 572             }
 573             sb.append(c);
 574         }
 575         return sb.toString();
 576     }
 577 
 578 
 579     /**
 580      * Serializes only the domain name string, for compactness and to avoid
 581      * any implementation dependency.
 582      *
 583      * @serialdata      The domain name string.
 584      */
 585     private void writeObject(java.io.ObjectOutputStream s)
 586             throws java.io.IOException {
 587         s.writeObject(toString());
 588     }
 589 
 590     private void readObject(java.io.ObjectInputStream s)
 591             throws java.io.IOException, ClassNotFoundException {
 592         try {
 593             parse((String) s.readObject());
 594         } catch (InvalidNameException e) {
 595             // shouldn't happen
 596             throw new java.io.StreamCorruptedException(
 597                     "Invalid name: " + domain);
 598         }
 599     }
 600 
 601     private static final long serialVersionUID = 7040187611324710271L;
 602 }