--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.dns.client/share/classes/jdk/dns/client/internal/DnsName.java Thu Oct 31 16:16:21 2019 +0000
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.dns.client.internal;
+
+import jdk.dns.client.ex.DnsInvalidNameException;
+
+import java.util.ArrayList;
+
+/**
+ * {@code DnsName} implements compound names for DNS as specified by
+ * RFCs 1034 and 1035, and as updated and clarified by RFCs 1123 and 2181.
+ *
+ * <p> The labels in a domain name correspond to JNDI atomic names.
+ * Each label must be less than 64 octets in length, and only the
+ * optional root label at the end of the name may be 0 octets long.
+ * The sum of the lengths of all labels in a name, plus the number of
+ * non-root labels plus 1, must be less than 256. The textual
+ * representation of a domain name consists of the labels, escaped as
+ * needed, dot-separated, and ordered right-to-left.
+ *
+ * <p> A label consists of a sequence of octets, each of which may
+ * have any value from 0 to 255.
+ *
+ * <p> <em>Host names</em> are a subset of domain names.
+ * Their labels contain only ASCII letters, digits, and hyphens, and
+ * none may begin or end with a hyphen. While names not conforming to
+ * these rules may be valid domain names, they will not be usable by a
+ * number of DNS applications, and should in most cases be avoided.
+ *
+ * <p> DNS does not specify an encoding (such as UTF-8) to use for
+ * octets with non-ASCII values. As of this writing there is some
+ * work going on in this area, but it is not yet finalized.
+ * {@code DnsName} currently converts any non-ASCII octets into
+ * characters using ISO-LATIN-1 encoding, in effect taking the
+ * value of each octet and storing it directly into the low-order byte
+ * of a Java character and <i>vice versa</i>. As a consequence, no
+ * character in a DNS name will ever have a non-zero high-order byte.
+ * When the work on internationalizing domain names has stabilized
+ * (see for example <i>draft-ietf-idn-idna-10.txt</i>), {@code DnsName}
+ * may be updated to conform to that work.
+ *
+ * <p> Backslash ({@code \}) is used as the escape character in the
+ * textual representation of a domain name. The character sequence
+ * `{@code \DDD}', where {@code DDD} is a 3-digit decimal number
+ * (with leading zeros if needed), represents the octet whose value
+ * is {@code DDD}. The character sequence `{@code \C}', where
+ * {@code C} is a character other than {@code '0'} through
+ * {@code '9'}, represents the octet whose value is that of
+ * {@code C} (again using ISO-LATIN-1 encoding); this is particularly
+ * useful for escaping {@code '.'} or backslash itself. Backslash is
+ * otherwise not allowed in a domain name. Note that escape characters
+ * are interpreted when a name is parsed. So, for example, the character
+ * sequences `{@code S}', `{@code \S}', and `{@code \083}' each
+ * represent the same one-octet name. The {@code toString()} method
+ * does not generally insert escape sequences except where necessary.
+ * If, however, the {@code DnsName} was constructed using unneeded
+ * escapes, those escapes may appear in the {@code toString} result.
+ *
+ * <p> Atomic names passed as parameters to methods of
+ * {@code DnsName}, and those returned by them, are unescaped. So,
+ * for example, <code>(new DnsName()).add("a.b")</code> creates an
+ * object representing the one-label domain name {@code a\.b}, and
+ * calling {@code get(0)} on this object returns {@code "a.b"}.
+ *
+ * <p> While DNS names are case-preserving, comparisons between them
+ * are case-insensitive. When comparing names containing non-ASCII
+ * octets, {@code DnsName} uses case-insensitive comparison
+ * between pairs of ASCII values, and exact binary comparison
+ * otherwise.
+ *
+ * <p> A {@code DnsName} instance is not synchronized against
+ * concurrent access by multiple threads.
+ *
+ * @author Scott Seligman
+ */
+
+// Stripped copy with removed serialization code and jndi.Name interface
+
+public final class DnsName {
+
+ // If non-null, the domain name represented by this DnsName.
+ private String domain = "";
+
+ // The labels of this domain name, as a list of strings. Index 0
+ // corresponds to the leftmost (least significant) label: note that
+ // this is the reverse of the ordering used by the Name interface.
+ private ArrayList<String> labels = new ArrayList<>();
+
+ // The number of octets needed to carry this domain name in a DNS
+ // packet. Equal to the sum of the lengths of each label, plus the
+ // number of non-root labels, plus 1. Must remain less than 256.
+ private short octets = 1;
+
+
+ /**
+ * Constructs a {@code DnsName} representing the empty domain name.
+ */
+ public DnsName() {
+ }
+
+ /**
+ * Constructs a {@code DnsName} representing a given domain name.
+ *
+ * @param name the domain name to parse
+ * @throws DnsInvalidNameException if {@code name} does not conform
+ * to DNS syntax.
+ */
+ public DnsName(String name) throws DnsInvalidNameException {
+ parse(name);
+ }
+
+ /*
+ * Returns a new DnsName with its name components initialized to
+ * the components of "n" in the range [beg,end). Indexing is as
+ * for the Name interface, with 0 being the most significant.
+ */
+ private DnsName(DnsName n, int beg, int end) {
+ // Compute indexes into "labels", which has least-significant label
+ // at index 0 (opposite to the convention used for "beg" and "end").
+ int b = n.size() - end;
+ int e = n.size() - beg;
+ labels.addAll(n.labels.subList(b, e));
+
+ if (size() == n.size()) {
+ domain = n.domain;
+ octets = n.octets;
+ } else {
+ for (String label : labels) {
+ if (label.length() > 0) {
+ octets += (short) (label.length() + 1);
+ }
+ }
+ }
+ }
+
+
+ public String toString() {
+ if (domain == null) {
+ StringBuilder buf = new StringBuilder();
+ for (String label : labels) {
+ if (buf.length() > 0 || label.length() == 0) {
+ buf.append('.');
+ }
+ escape(buf, label);
+ }
+ domain = buf.toString();
+ }
+ return domain;
+ }
+
+ /**
+ * Does this domain name follow <em>host name</em> syntax?
+ */
+ public boolean isHostName() {
+ for (String label : labels) {
+ if (!isHostNameLabel(label)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public short getOctets() {
+ return octets;
+ }
+
+ public int size() {
+ return labels.size();
+ }
+
+ public boolean isEmpty() {
+ return (size() == 0);
+ }
+
+ public int hashCode() {
+ int h = 0;
+ for (int i = 0; i < size(); i++) {
+ h = 31 * h + getKey(i).hashCode();
+ }
+ return h;
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DnsName)) {
+ return false;
+ }
+ DnsName n = (DnsName) obj;
+ return ((size() == n.size()) && // shortcut: do sizes differ?
+ (compareTo(obj) == 0));
+ }
+
+ public int compareTo(Object obj) {
+ DnsName n = (DnsName) obj;
+ return compareRange(0, size(), n); // never 0 if sizes differ
+ }
+
+ public String get(int pos) {
+ if (pos < 0 || pos >= size()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ int i = size() - pos - 1; // index of "pos" component in "labels"
+ return labels.get(i);
+ }
+
+ public DnsName getPrefix(int pos) {
+ return new DnsName(this, 0, pos);
+ }
+
+ public DnsName getSuffix(int pos) {
+ return new DnsName(this, pos, size());
+ }
+
+ public Object clone() {
+ return new DnsName(this, 0, size());
+ }
+
+ public DnsName add(int pos, String comp) throws DnsInvalidNameException {
+ if (pos < 0 || pos > size()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ // Check for empty labels: may have only one, and only at end.
+ int len = comp.length();
+ if ((pos > 0 && len == 0) ||
+ (pos == 0 && hasRootLabel())) {
+ throw new DnsInvalidNameException(
+ "Empty label must be the last label in a domain name");
+ }
+ // Check total name length.
+ if (len > 0) {
+ if (octets + len + 1 >= 256) {
+ throw new DnsInvalidNameException("Name too long");
+ }
+ octets += (short) (len + 1);
+ }
+
+ int i = size() - pos; // index for insertion into "labels"
+ verifyLabel(comp);
+ labels.add(i, comp);
+
+ domain = null; // invalidate "domain"
+ return this;
+ }
+
+ public DnsName addAll(int pos, DnsName n) throws DnsInvalidNameException {
+ // "n" is a DnsName so we can insert it as a whole, rather than
+ // verifying and inserting it component-by-component.
+ // More code, but less work.
+
+ if (n.isEmpty()) {
+ return this;
+ }
+ // Check for empty labels: may have only one, and only at end.
+ if ((pos > 0 && n.hasRootLabel()) ||
+ (pos == 0 && hasRootLabel())) {
+ throw new DnsInvalidNameException(
+ "Empty label must be the last label in a domain name");
+ }
+
+ short newOctets = (short) (octets + n.octets - 1);
+ if (newOctets > 255) {
+ throw new DnsInvalidNameException("Name too long");
+ }
+ octets = newOctets;
+ int i = size() - pos; // index for insertion into "labels"
+ labels.addAll(i, n.labels);
+
+ // Preserve "domain" if we're appending or prepending,
+ // otherwise invalidate it.
+ if (isEmpty()) {
+ domain = n.domain;
+ } else if (domain == null || n.domain == null) {
+ domain = null;
+ } else if (pos == 0) {
+ domain += (n.domain.equals(".") ? "" : ".") + n.domain;
+ } else if (pos == size()) {
+ domain = n.domain + (domain.equals(".") ? "" : ".") + domain;
+ } else {
+ domain = null;
+ }
+ return this;
+ }
+
+ boolean hasRootLabel() {
+ return (!isEmpty() &&
+ get(0).isEmpty());
+ }
+
+ /*
+ * Helper method for public comparison methods. Lexicographically
+ * compares components of this name in the range [beg,end) with
+ * all components of "n". Indexing is as for the Name interface,
+ * with 0 being the most significant. Returns negative, zero, or
+ * positive as these name components are less than, equal to, or
+ * greater than those of "n".
+ */
+ private int compareRange(int beg, int end, DnsName n) {
+ // aee: Removed CompositeName ClassCastException generation here
+
+ // Loop through labels, starting with most significant.
+ int minSize = Math.min(end - beg, n.size());
+ for (int i = 0; i < minSize; i++) {
+ String label1 = get(i + beg);
+ String label2 = n.get(i);
+
+ // int j = size() - (i + beg) - 1; // index of label1 in "labels"
+ // assert (label1 == labels.get(j));
+
+ int c = compareLabels(label1, label2);
+ if (c != 0) {
+ return c;
+ }
+ }
+ return ((end - beg) - n.size()); // longer range wins
+ }
+
+ /*
+ * Returns a key suitable for hashing the label at index i.
+ * Indexing is as for the Name interface, with 0 being the most
+ * significant.
+ */
+ String getKey(int i) {
+ return keyForLabel(get(i));
+ }
+
+
+ /*
+ * Parses a domain name, setting the values of instance vars accordingly.
+ */
+ private void parse(String name) throws DnsInvalidNameException {
+
+ StringBuilder label = new StringBuilder(); // label being parsed
+
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+
+ if (c == '\\') { // found an escape sequence
+ c = getEscapedOctet(name, i++);
+ if (isDigit(name.charAt(i))) { // sequence is \DDD
+ i += 2; // consume remaining digits
+ }
+ label.append(c);
+
+ } else if (c != '.') { // an unescaped octet
+ label.append(c);
+
+ } else { // found '.' separator
+ add(0, label.toString()); // check syntax, then add label
+ // to end of name
+ label.delete(0, i); // clear buffer for next label
+ }
+ }
+
+ // If name is neither "." nor "", the octets (zero or more)
+ // from the rightmost dot onward are now added as the final
+ // label of the name. Those two are special cases in that for
+ // all other domain names, the number of labels is one greater
+ // than the number of dot separators.
+ if (!name.isEmpty() && !name.equals(".")) {
+ add(0, label.toString());
+ }
+
+ domain = name; // do this last, since add() sets it to null
+ }
+
+ /*
+ * Returns (as a char) the octet indicated by the escape sequence
+ * at a given position within a domain name.
+ * @throws InvalidNameException if a valid escape sequence is not found.
+ */
+ private static char getEscapedOctet(String name, int pos)
+ throws DnsInvalidNameException {
+ try {
+ // assert (name.charAt(pos) == '\\');
+ char c1 = name.charAt(++pos);
+ if (isDigit(c1)) { // sequence is `\DDD'
+ char c2 = name.charAt(++pos);
+ char c3 = name.charAt(++pos);
+ if (isDigit(c2) && isDigit(c3)) {
+ return (char)
+ ((c1 - '0') * 100 + (c2 - '0') * 10 + (c3 - '0'));
+ } else {
+ throw new DnsInvalidNameException(
+ "Invalid escape sequence in " + name);
+ }
+ } else { // sequence is `\C'
+ return c1;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new DnsInvalidNameException(
+ "Invalid escape sequence in " + name);
+ }
+ }
+
+ /*
+ * Checks that this label is valid.
+ * @throws InvalidNameException if label is not valid.
+ */
+ private static void verifyLabel(String label) throws DnsInvalidNameException {
+ if (label.length() > 63) {
+ throw new DnsInvalidNameException(
+ "Label exceeds 63 octets: " + label);
+ }
+ // Check for two-byte characters.
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if ((c & 0xFF00) != 0) {
+ throw new DnsInvalidNameException(
+ "Label has two-byte char: " + label);
+ }
+ }
+ }
+
+ /*
+ * Does this label conform to host name syntax?
+ */
+ private static boolean isHostNameLabel(String label) {
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if (!isHostNameChar(c)) {
+ return false;
+ }
+ }
+ return !(label.startsWith("-") || label.endsWith("-"));
+ }
+
+ private static boolean isHostNameChar(char c) {
+ return (c == '-' ||
+ c >= 'a' && c <= 'z' ||
+ c >= 'A' && c <= 'Z' ||
+ c >= '0' && c <= '9');
+ }
+
+ private static boolean isDigit(char c) {
+ return (c >= '0' && c <= '9');
+ }
+
+ /*
+ * Append a label to buf, escaping as needed.
+ */
+ private static void escape(StringBuilder buf, String label) {
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if (c == '.' || c == '\\') {
+ buf.append('\\');
+ }
+ buf.append(c);
+ }
+ }
+
+ /*
+ * Compares two labels, ignoring case for ASCII values.
+ * Returns negative, zero, or positive as the first label
+ * is less than, equal to, or greater than the second.
+ * See keyForLabel().
+ */
+ private static int compareLabels(String label1, String label2) {
+ int min = Math.min(label1.length(), label2.length());
+ for (int i = 0; i < min; i++) {
+ char c1 = label1.charAt(i);
+ char c2 = label2.charAt(i);
+ if (c1 >= 'A' && c1 <= 'Z') {
+ c1 += 'a' - 'A'; // to lower case
+ }
+ if (c2 >= 'A' && c2 <= 'Z') {
+ c2 += 'a' - 'A'; // to lower case
+ }
+ if (c1 != c2) {
+ return (c1 - c2);
+ }
+ }
+ return (label1.length() - label2.length()); // the longer one wins
+ }
+
+ /*
+ * Returns a key suitable for hashing a label. Two labels map to
+ * the same key iff they are equal, taking possible case-folding
+ * into account. See compareLabels().
+ */
+ private static String keyForLabel(String label) {
+ StringBuilder sb = new StringBuilder(label.length());
+ for (int i = 0; i < label.length(); i++) {
+ char c = label.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ c += 'a' - 'A'; // to lower case
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+}