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 * {@code DnsName} 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 * {@code DnsName} 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>), {@code DnsName} 67 * may be updated to conform to that work. 68 * 69 * <p> Backslash ({@code \}) is used as the escape character in the 70 * textual representation of a domain name. The character sequence 71 * `{@code \DDD}', where {@code DDD} is a 3-digit decimal number 72 * (with leading zeros if needed), represents the octet whose value 73 * is {@code DDD}. The character sequence `{@code \C}', where 74 * {@code C} is a character other than {@code '0'} through 75 * {@code '9'}, represents the octet whose value is that of 76 * {@code C} (again using ISO-LATIN-1 encoding); this is particularly 77 * useful for escaping {@code '.'} 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 `{@code S}', `{@code \S}', and `{@code \083}' each 81 * represent the same one-octet name. The {@code toString()} method 82 * does not generally insert escape sequences except where necessary. 83 * If, however, the {@code DnsName} was constructed using unneeded 84 * escapes, those escapes may appear in the {@code toString} result. 85 * 86 * <p> Atomic names passed as parameters to methods of 87 * {@code DnsName}, and those returned by them, are unescaped. So, 88 * for example, <code>(new DnsName()).add("a.b")</code> creates an 89 * object representing the one-label domain name {@code a\.b}, and 90 * calling {@code get(0)} on this object returns {@code "a.b"}. 91 * 92 * <p> While DNS names are case-preserving, comparisons between them 93 * are case-insensitive. When comparing names containing non-ASCII 94 * octets, {@code DnsName} uses case-insensitive comparison 95 * between pairs of ASCII values, and exact binary comparison 96 * otherwise. 97 98 * <p> A {@code DnsName} 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 {@code DnsName} representing the empty domain name. 123 */ 124 public DnsName() { 125 } 126 127 /** 128 * Constructs a {@code DnsName} representing a given domain name. 129 * 130 * @param name the domain name to parse 131 * @throws InvalidNameException if {@code name} 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).isEmpty()); 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.isEmpty() && !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 }