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