blob: f723ac394db7b0a27cb6a8e5f2e9eb2adceccbef [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2004 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.jndi.dns;
27
28
29import java.util.ArrayList;
30import java.util.Comparator;
31import java.util.Enumeration;
32import java.util.Iterator;
33
34import 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&nbsp;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
106public 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}