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