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