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