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.dnslabel;
012
013import java.io.ByteArrayOutputStream;
014import java.util.Locale;
015
016/**
017 * A DNS label is an individual component of a DNS name. Labels are usually shown separated by dots.
018 * 
019 * @see <a href="https://tools.ietf.org/html/rfc5890#section-2.2">RFC 5890 § 2.2. DNS-Related Terminology</a>
020 * @author Florian Schmaus
021 *
022 */
023public abstract class DnsLabel implements CharSequence {
024
025    /**
026     * The maximum length of a DNS label in octets.
027     *
028     * @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035 § 2.3.4.</a>
029     */
030    public static final int MAX_LABEL_LENGTH_IN_OCTETS = 63;
031
032    /**
033     * Whether or not the DNS label is validated on construction.
034     */
035    public static boolean VALIDATE = true;
036
037    public final String label;
038
039    protected DnsLabel(String label) {
040        this.label = label;
041
042        if (!VALIDATE) {
043            return;
044        }
045
046        setBytesIfRequired();
047        if (byteCache.length > MAX_LABEL_LENGTH_IN_OCTETS) {
048            throw new LabelToLongException(label);
049        }
050    }
051
052    private transient String internationalizedRepresentation;
053
054    public final String getInternationalizedRepresentation() {
055        if (internationalizedRepresentation == null) {
056            internationalizedRepresentation = getInternationalizedRepresentationInternal();
057        }
058        return internationalizedRepresentation;
059    }
060
061    protected String getInternationalizedRepresentationInternal() {
062        return label;
063    }
064
065    public final String getLabelType() {
066        return getClass().getSimpleName();
067    }
068
069    @Override
070    public final int length() {
071        return label.length();
072    }
073
074    @Override
075    public final char charAt(int index) {
076        return label.charAt(index);
077    }
078
079    @Override
080    public final CharSequence subSequence(int start, int end) {
081        return label.subSequence(start, end);
082    }
083
084    @Override
085    public final String toString() {
086        return label;
087    }
088
089    @Override
090    public final boolean equals(Object other) {
091        if (!(other instanceof DnsLabel)) {
092            return false;
093        }
094        DnsLabel otherDnsLabel = (DnsLabel) other;
095        return label.equals(otherDnsLabel.label);
096    }
097
098    @Override
099    public final int hashCode() {
100        return label.hashCode();
101    }
102
103    private transient DnsLabel lowercasedVariant;
104
105    public final DnsLabel asLowercaseVariant() {
106        if (lowercasedVariant == null) {
107            String lowercaseLabel = label.toLowerCase(Locale.US);
108            lowercasedVariant = DnsLabel.from(lowercaseLabel);
109        }
110        return lowercasedVariant;
111    }
112
113    private transient byte[] byteCache;
114
115    private void setBytesIfRequired() {
116        if (byteCache == null) {
117            byteCache = label.getBytes();
118        }
119    }
120
121    public final void writeToBoas(ByteArrayOutputStream byteArrayOutputStream) {
122        setBytesIfRequired();
123
124        byteArrayOutputStream.write(byteCache.length);
125        byteArrayOutputStream.write(byteCache, 0, byteCache.length);
126    }
127
128    public static DnsLabel from(String label) {
129        if (label == null || label.isEmpty()) {
130            throw new IllegalArgumentException("Label is null or empty");
131        }
132
133        if (LdhLabel.isLdhLabel(label)) {
134            return LdhLabel.fromInternal(label);
135        }
136
137        return NonLdhLabel.fromInternal(label);
138    }
139
140    public static DnsLabel[] from(String[] labels) {
141        DnsLabel[] res = new DnsLabel[labels.length];
142
143        for (int i = 0; i < labels.length; i++) {
144            res[i] = DnsLabel.from(labels[i]);
145        }
146
147        return res;
148    }
149
150    public static boolean isIdnAcePrefixed(String string) {
151        return string.toLowerCase(Locale.US).startsWith("xn--");
152    }
153
154    public static class LabelToLongException extends IllegalArgumentException {
155
156        /**
157         * 
158         */
159        private static final long serialVersionUID = 1L;
160
161        public final String label;
162
163        LabelToLongException(String label) {
164            this.label = label;
165        }
166    }
167}