001/*
002 * Copyright 2015-2018 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.dnsname;
012
013import java.io.ByteArrayOutputStream;
014import java.io.DataInputStream;
015import java.io.IOException;
016import java.io.OutputStream;
017import java.io.Serializable;
018import java.util.Arrays;
019import java.util.HashSet;
020import java.util.Locale;
021
022import org.minidns.dnslabel.DnsLabel;
023import org.minidns.idna.MiniDnsIdna;
024
025/**
026 * A DNS name, also called "domain name". A DNS name consists of multiple 'labels' and is subject to certain restrictions (see
027 * for example <a href="https://tools.ietf.org/html/rfc3696#section-2">RFC 3696 § 2.</a>).
028 * <p>
029 * Instances of this class can be created by using {@link #from(String)}.
030 * </p>
031 * <p>
032 * This class holds three representations of a DNS name: ACE, raw ACE and IDN. ACE (ASCII Compatible Encoding), which
033 * can be accessed via {@link #ace}, represents mostly the data that got send over the wire. But since DNS names are
034 * case insensitive, the ACE value is normalized to lower case. You can use {@link #getRawAce()} to get the raw ACE data
035 * that was received, which possibly includes upper case characters. The IDN (Internationalized Domain Name), that is
036 * the DNS name as it should be shown to the user, can be retrieved using {@link #asIdn()}.
037 * </p>
038 *
039 * @see <a href="https://tools.ietf.org/html/rfc3696">RFC 3696</a>
040 * @author Florian Schmaus
041 *
042 */
043public class DnsName implements CharSequence, Serializable, Comparable<DnsName> {
044
045    /**
046     * 
047     */
048    private static final long serialVersionUID = 1L;
049
050    /**
051     * @see <a href="https://www.ietf.org/rfc/rfc3490.txt">RFC 3490 § 3.1 1.</a>
052     */
053    private static final String LABEL_SEP_REGEX = "[.\u3002\uFF0E\uFF61]";
054
055    /**
056     * @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035 § 2.3.4.</a<
057     */
058    static final int MAX_DNSNAME_LENGTH_IN_OCTETS = 255;
059
060    public static final int MAX_LABELS = 128;
061
062    public static final DnsName ROOT = new DnsName(".");
063
064    public static final DnsName IN_ADDR_ARPA = new DnsName("in-addr.arpa");
065
066    public static final DnsName IP6_ARPA = new DnsName("ip6.arpa");
067
068    /**
069     * Whether or not the DNS name is validated on construction.
070     */
071    public static boolean VALIDATE = true;
072
073    /**
074     * The DNS name in ASCII Compatible Encoding (ACE).
075     */
076    public final String ace;
077
078    /**
079     * The DNS name in raw format, i.e. as it was received from the remote server. This means that compared to
080     * {@link #ace}, this String may not be lower-cased.
081     */
082    private final String rawAce;
083
084    private transient byte[] bytes;
085
086    private transient byte[] rawBytes;
087
088    private transient String idn;
089
090    private transient String domainpart;
091
092    private transient String hostpart;
093
094    /**
095     * The labels in <b>reverse</b> order.
096     */
097    private transient DnsLabel[] labels;
098
099    private transient DnsLabel[] rawLabels;
100
101    private transient int hashCode;
102
103    private int size = -1;
104
105    private DnsName(String name) {
106        this(name, true);
107    }
108
109    private DnsName(String name, boolean inAce) {
110        if (name.isEmpty()) {
111            rawAce = ROOT.rawAce;
112        } else {
113            final int nameLength = name.length();
114            final int nameLastPos = nameLength - 1;
115
116            // Strip potential trailing dot. N.B. that we require nameLength > 2, because we don't want to strip the one
117            // character string containing only a single dot to the empty string.
118            if (nameLength >= 2 && name.charAt(nameLastPos) == '.') {
119                name = name.subSequence(0, nameLastPos).toString();
120            }
121
122            if (inAce) {
123                // Name is already in ACE format.
124                rawAce = name;
125            } else {
126                rawAce = MiniDnsIdna.toASCII(name);
127            }
128        }
129
130        ace = rawAce.toLowerCase(Locale.US);
131
132        if (!VALIDATE) {
133            return;
134        }
135
136        // Validate the DNS name.
137        validateMaxDnsnameLengthInOctets();
138    }
139
140    private DnsName(DnsLabel[] rawLabels, boolean validateMaxDnsnameLength) {
141        this.rawLabels = rawLabels;
142        this.labels = new DnsLabel[rawLabels.length];
143
144        int size = 0;
145        for (int i = 0; i < rawLabels.length; i++) {
146            size += rawLabels[i].length() + 1;
147            labels[i] = rawLabels[i].asLowercaseVariant();
148        }
149
150        rawAce = labelsToString(rawLabels, size);
151        ace    = labelsToString(labels,    size);
152
153        if (!validateMaxDnsnameLength || !VALIDATE) {
154            return;
155        }
156
157        validateMaxDnsnameLengthInOctets();
158    }
159
160    private static String labelsToString(DnsLabel[] labels, int stringLength) {
161        StringBuilder sb = new StringBuilder(stringLength);
162        for (int i = labels.length - 1; i >= 0; i--) {
163            sb.append(labels[i]).append('.');
164        }
165        sb.setLength(sb.length() - 1);
166        return sb.toString();
167    }
168
169    private void validateMaxDnsnameLengthInOctets() {
170        setBytesIfRequired();
171        if (bytes.length > MAX_DNSNAME_LENGTH_IN_OCTETS) {
172            throw new InvalidDnsNameException.DNSNameTooLongException(ace, bytes);
173        }
174    }
175
176    public void writeToStream(OutputStream os) throws IOException {
177        setBytesIfRequired();
178        os.write(bytes);
179    }
180
181    /**
182     * Serialize a domain name under IDN rules.
183     *
184     * @return The binary domain name representation.
185     */
186    public byte[] getBytes() {
187        setBytesIfRequired();
188        return bytes.clone();
189    }
190
191    public byte[] getRawBytes() {
192        if (rawBytes == null) {
193            setLabelsIfRequired();
194            rawBytes = toBytes(rawLabels);
195        }
196
197        return rawBytes.clone();
198    }
199
200    private void setBytesIfRequired() {
201        if (bytes != null)
202            return;
203
204        setLabelsIfRequired();
205        bytes = toBytes(labels);
206    }
207
208    private static byte[] toBytes(DnsLabel[] labels) {
209        ByteArrayOutputStream baos = new ByteArrayOutputStream(64);
210        for (int i = labels.length - 1; i >= 0; i--) {
211            labels[i].writeToBoas(baos);
212        }
213
214        baos.write(0);
215
216        assert (baos.size() <= MAX_DNSNAME_LENGTH_IN_OCTETS);
217
218        return baos.toByteArray();
219    }
220
221    private void setLabelsIfRequired() {
222        if (labels != null && rawLabels != null) return;
223
224        if (isRootLabel()) {
225            rawLabels = labels = new DnsLabel[0];
226            return;
227        }
228
229        labels = getLabels(ace);
230        rawLabels = getLabels(rawAce);
231    }
232
233    private static DnsLabel[] getLabels(String ace) {
234        String[] labels = ace.split(LABEL_SEP_REGEX, MAX_LABELS);
235
236        // Reverse the labels, so that 'foo, example, org' becomes 'org, example, foo'.
237        for (int i = 0; i < labels.length / 2; i++) {
238            String t = labels[i];
239            int j = labels.length - i - 1;
240            labels[i] = labels[j];
241            labels[j] = t;
242        }
243
244        try {
245            return DnsLabel.from(labels);
246        } catch (DnsLabel.LabelToLongException e) {
247            throw new InvalidDnsNameException.LabelTooLongException(ace, e.label);
248        }
249    }
250
251    public String getRawAce() {
252        return rawAce;
253    }
254
255    public String asIdn() {
256        if (idn != null)
257            return idn;
258
259        idn = MiniDnsIdna.toUnicode(ace);
260        return idn;
261    }
262
263    /**
264     * Domainpart in ACE representation.
265     *
266     * @return the domainpart in ACE representation.
267     */
268    public String getDomainpart() {
269        setHostnameAndDomainpartIfRequired();
270        return domainpart;
271    }
272
273    /**
274     * Hostpart in ACE representation.
275     *
276     * @return the hostpart in ACE representation.
277     */
278    public String getHostpart() {
279        setHostnameAndDomainpartIfRequired();
280        return hostpart;
281    }
282
283    private void setHostnameAndDomainpartIfRequired() {
284        if (hostpart != null) return;
285
286        String[] parts = ace.split(LABEL_SEP_REGEX, 2);
287        hostpart = parts[0];
288        if (parts.length > 1) {
289            domainpart = parts[1];
290        } else {
291            domainpart = "";
292        }
293    }
294
295    public int size() {
296        if (size < 0) {
297            if (isRootLabel()) {
298                size = 1;
299            } else {
300                size = ace.length() + 2;
301            }
302        }
303        return size;
304    }
305
306    @Override
307    public int length() {
308        return ace.length();
309    }
310
311    @Override
312    public char charAt(int index) {
313        return ace.charAt(index);
314    }
315
316    @Override
317    public CharSequence subSequence(int start, int end) {
318        return ace.subSequence(start, end);
319    }
320
321    @Override
322    public String toString() {
323        return ace;
324    }
325
326    public static DnsName from(CharSequence name) {
327        return from(name.toString());
328    }
329
330    public static DnsName from(String name) {
331        return new DnsName(name, false);
332    }
333
334    /**
335     * Create a DNS name by "concatenating" the child under the parent name. The child can also be seen as the "left"
336     * part of the resulting DNS name and the parent is the "right" part.
337     * <p>
338     * For example using "i.am.the.child" as child and "of.this.parent.example" as parent, will result in a DNS name:
339     * "i.am.the.child.of.this.parent.example".
340     * </p>
341     *
342     * @param child the child DNS name.
343     * @param parent the parent DNS name.
344     * @return the resulting of DNS name.
345     */
346    public static DnsName from(DnsName child, DnsName parent) {
347        child.setLabelsIfRequired();
348        parent.setLabelsIfRequired();
349
350        DnsLabel[] rawLabels = new DnsLabel[child.rawLabels.length + parent.rawLabels.length];
351        System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length);
352        System.arraycopy(child.rawLabels, 0, rawLabels, parent.rawLabels.length, child.rawLabels.length);
353        return new DnsName(rawLabels, true);
354    }
355
356    public static DnsName from(DnsName... nameComponents) {
357        int labelCount = 0;
358        for (DnsName component : nameComponents) {
359            component.setLabelsIfRequired();
360            labelCount += component.rawLabels.length;
361        }
362
363        DnsLabel[] rawLabels = new DnsLabel[labelCount];
364        int destLabelPos = 0;
365        for (int i = nameComponents.length - 1; i >= 0; i--) {
366            DnsName component = nameComponents[i];
367            System.arraycopy(component.rawLabels, 0, rawLabels, destLabelPos, component.rawLabels.length);
368            destLabelPos += component.rawLabels.length;
369        }
370
371        return new DnsName(rawLabels, true);
372    }
373
374    public static DnsName from(String[] parts) {
375        DnsLabel[] rawLabels = DnsLabel.from(parts);
376
377        return new DnsName(rawLabels, true);
378    }
379
380    /**
381     * Parse a domain name starting at the current offset and moving the input
382     * stream pointer past this domain name (even if cross references occure).
383     *
384     * @param dis  The input stream.
385     * @param data The raw data (for cross references).
386     * @return The domain name string.
387     * @throws IOException Should never happen.
388     */
389    public static DnsName parse(DataInputStream dis, byte data[])
390            throws IOException {
391        int c = dis.readUnsignedByte();
392        if ((c & 0xc0) == 0xc0) {
393            c = ((c & 0x3f) << 8) + dis.readUnsignedByte();
394            HashSet<Integer> jumps = new HashSet<Integer>();
395            jumps.add(c);
396            return parse(data, c, jumps);
397        }
398        if (c == 0) {
399            return DnsName.ROOT;
400        }
401        byte b[] = new byte[c];
402        dis.readFully(b);
403
404        String childLabelString = new String(b);
405        DnsName child = new DnsName(childLabelString);
406
407        DnsName parent = parse(dis, data);
408        return DnsName.from(child, parent);
409    }
410
411    /**
412     * Parse a domain name starting at the given offset.
413     *
414     * @param data   The raw data.
415     * @param offset The offset.
416     * @param jumps  The list of jumps (by now).
417     * @return The parsed domain name.
418     * @throws IllegalStateException on cycles.
419     */
420    private static DnsName parse(byte data[], int offset, HashSet<Integer> jumps)
421            throws IllegalStateException {
422        int c = data[offset] & 0xff;
423        if ((c & 0xc0) == 0xc0) {
424            c = ((c & 0x3f) << 8) + (data[offset + 1] & 0xff);
425            if (jumps.contains(c)) {
426                throw new IllegalStateException("Cyclic offsets detected.");
427            }
428            jumps.add(c);
429            return parse(data, c, jumps);
430        }
431        if (c == 0) {
432            return DnsName.ROOT;
433        }
434
435        String childLabelString = new String(data, offset + 1, c);
436        DnsName child = new DnsName(childLabelString);
437
438        DnsName parent = parse(data, offset + 1 + c, jumps);
439        return DnsName.from(child, parent);
440    }
441
442    @Override
443    public int compareTo(DnsName other) {
444        return ace.compareTo(other.ace);
445    }
446
447    @Override
448    public boolean equals(Object other) {
449        if (other == null) return false;
450
451        if (other instanceof DnsName) {
452            DnsName otherDnsName = (DnsName) other;
453            setBytesIfRequired();
454            otherDnsName.setBytesIfRequired();
455            return Arrays.equals(bytes, otherDnsName.bytes);
456        }
457
458        return false;
459    }
460
461    @Override
462    public int hashCode() {
463        if (hashCode == 0 && !isRootLabel()) {
464            setBytesIfRequired();
465            hashCode = Arrays.hashCode(bytes);
466        }
467        return hashCode;
468    }
469
470    public boolean isDirectChildOf(DnsName parent) {
471        setLabelsIfRequired();
472        parent.setLabelsIfRequired();
473        int parentLabelsCount = parent.labels.length;
474
475        if (labels.length - 1 != parentLabelsCount)
476            return false;
477
478        for (int i = 0; i < parent.labels.length; i++) {
479            if (!labels[i].equals(parent.labels[i]))
480                return false;
481        }
482
483        return true;
484    }
485
486    public boolean isChildOf(DnsName parent) {
487        setLabelsIfRequired();
488        parent.setLabelsIfRequired();
489
490        if (labels.length < parent.labels.length)
491            return false;
492
493        for (int i = 0; i < parent.labels.length; i++) {
494            if (!labels[i].equals(parent.labels[i]))
495                return false;
496        }
497
498        return true;
499    }
500
501    public int getLabelCount() {
502        setLabelsIfRequired();
503        return labels.length;
504    }
505
506    /**
507     * Get a copy of the labels of this DNS name. The resulting array will contain the labels in reverse order, that is,
508     * the top-level domain will be at res[0].
509     *
510     * @return an array of the labels in reverse order.
511     */
512    public DnsLabel[] getLabels() {
513        setLabelsIfRequired();
514        return labels.clone();
515    }
516
517    /**
518     * Get a copy of the raw labels of this DNS name. The resulting array will contain the labels in reverse order, that is,
519     * the top-level domain will be at res[0].
520     *
521     * @return an array of the raw labels in reverse order.
522     */
523    public DnsLabel[] getRawLabels() {
524        setLabelsIfRequired();
525        return rawLabels.clone();
526    }
527
528    public DnsName stripToLabels(int labelCount) {
529        setLabelsIfRequired();
530
531        if (labelCount > labels.length) {
532            throw new IllegalArgumentException();
533        }
534
535        if (labelCount == labels.length) {
536            return this;
537        }
538
539        if (labelCount == 0) {
540            return ROOT;
541        }
542
543        DnsLabel[] stripedLabels = Arrays.copyOfRange(rawLabels, 0, labelCount);
544
545        return new DnsName(stripedLabels, false);
546    }
547
548    /**
549     * Return the parent of this DNS label. Will return the root label if this label itself is the root label (because there is no parent of root).
550     * <p>
551     * For example:
552     * </p>
553     * <ul>
554     *  <li><code>"foo.bar.org".getParent() == "bar.org"</code></li>
555     *  <li><code> ".".getParent() == "."</code></li>
556     * </ul>
557     * @return the parent of this DNS label.
558     */
559    public DnsName getParent() {
560        if (isRootLabel()) return ROOT;
561        return stripToLabels(getLabelCount() - 1);
562    }
563
564    public boolean isRootLabel() {
565        return ace.isEmpty() || ace.equals(".");
566    }
567}