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