All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.minidns.dnslabel.DnsLabel Maven / Gradle / Ivy

/*
 * Copyright 2015-2024 the original author or authors
 *
 * This software is licensed under the Apache License, Version 2.0,
 * the GNU Lesser General Public License version 2 or later ("LGPL")
 * and the WTFPL.
 * You may choose either license to govern your use of this software only
 * upon the condition that you accept all of the terms of either
 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
 */
package org.minidns.dnslabel;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;

import org.minidns.util.SafeCharSequence;

/**
 * A DNS label is an individual component of a DNS name. Labels are usually shown separated by dots.
 * 

* This class implements {@link Comparable} which compares DNS labels according to the Canonical DNS Name Order as * specified in RFC 4034 § 6.1. *

*

* Note that as per RFC 2181 § 11 DNS labels may contain * any byte. *

* * @see RFC 5890 § 2.2. DNS-Related Terminology * @author Florian Schmaus * */ public abstract class DnsLabel extends SafeCharSequence implements Comparable { /** * The maximum length of a DNS label in octets. * * @see RFC 1035 § 2.3.4. */ public static final int MAX_LABEL_LENGTH_IN_OCTETS = 63; public static final DnsLabel WILDCARD_LABEL = DnsLabel.from("*"); /** * Whether or not the DNS label is validated on construction. */ public static boolean VALIDATE = true; public final String label; protected DnsLabel(String label) { this.label = label; if (!VALIDATE) { return; } setBytesIfRequired(); if (byteCache.length > MAX_LABEL_LENGTH_IN_OCTETS) { throw new LabelToLongException(label); } } private transient String internationalizedRepresentation; public final String getInternationalizedRepresentation() { if (internationalizedRepresentation == null) { internationalizedRepresentation = getInternationalizedRepresentationInternal(); } return internationalizedRepresentation; } protected String getInternationalizedRepresentationInternal() { return label; } public final String getLabelType() { return getClass().getSimpleName(); } private transient String safeToStringRepresentation; @Override public final String toString() { if (safeToStringRepresentation == null) { safeToStringRepresentation = toSafeRepesentation(label); } return safeToStringRepresentation; } /** * Get the raw label. Note that this may return a String containing null bytes. * Those Strings are notoriously difficult to handle from a security * perspective. Therefore it is recommended to use {@link #toString()} instead, * which will return a sanitized String. * * @return the raw label. * @since 1.1.0 */ public final String getRawLabel() { return label; } @Override public final boolean equals(Object other) { if (!(other instanceof DnsLabel)) { return false; } DnsLabel otherDnsLabel = (DnsLabel) other; return label.equals(otherDnsLabel.label); } @Override public final int hashCode() { return label.hashCode(); } private transient DnsLabel lowercasedVariant; public final DnsLabel asLowercaseVariant() { if (lowercasedVariant == null) { String lowercaseLabel = label.toLowerCase(Locale.US); lowercasedVariant = DnsLabel.from(lowercaseLabel); } return lowercasedVariant; } private transient byte[] byteCache; private void setBytesIfRequired() { if (byteCache == null) { byteCache = label.getBytes(StandardCharsets.US_ASCII); } } public final void writeToBoas(ByteArrayOutputStream byteArrayOutputStream) { setBytesIfRequired(); byteArrayOutputStream.write(byteCache.length); byteArrayOutputStream.write(byteCache, 0, byteCache.length); } @Override public final int compareTo(DnsLabel other) { String myCanonical = asLowercaseVariant().label; String otherCanonical = other.asLowercaseVariant().label; return myCanonical.compareTo(otherCanonical); } public static DnsLabel from(String label) { if (label == null || label.isEmpty()) { throw new IllegalArgumentException("Label is null or empty"); } if (LdhLabel.isLdhLabel(label)) { return LdhLabel.fromInternal(label); } return NonLdhLabel.fromInternal(label); } public static DnsLabel[] from(String[] labels) { DnsLabel[] res = new DnsLabel[labels.length]; for (int i = 0; i < labels.length; i++) { res[i] = DnsLabel.from(labels[i]); } return res; } public static boolean isIdnAcePrefixed(String string) { return string.toLowerCase(Locale.US).startsWith("xn--"); } public static String toSafeRepesentation(String dnsLabel) { if (consistsOnlyOfLettersDigitsHypenAndUnderscore(dnsLabel)) { // This label is safe, nothing to do. return dnsLabel; } StringBuilder sb = new StringBuilder(2 * dnsLabel.length()); for (int i = 0; i < dnsLabel.length(); i++) { char c = dnsLabel.charAt(i); if (isLdhOrMaybeUnderscore(c, true)) { sb.append(c); continue; } // Let's see if we found and unsafe char we want to replace. switch (c) { case '.': sb.append('●'); // U+25CF BLACK CIRCLE; break; case '\\': sb.append('⧷'); // U+29F7 REVERSE SOLIDUS WITH HORIZONTAL STROKE break; case '\u007f': // Convert DEL to U+2421 SYMBOL FOR DELETE sb.append('␡'); break; case ' ': sb.append('␣'); // U+2423 OPEN BOX break; default: if (c < 32) { // First convert the ASCI control codes to the Unicode Control Pictures int substituteAsInt = c + '\u2400'; assert substituteAsInt <= Character.MAX_CODE_POINT; char substitute = (char) substituteAsInt; sb.append(substitute); } else if (c < 127) { // Everything smaller than 127 is now safe to directly append. sb.append(c); } else if (c > 255) { throw new IllegalArgumentException("The string '" + dnsLabel + "' contains characters outside the 8-bit range: " + c + " at position " + i); } else { // Everything that did not match the previous conditions is explicitly escaped. sb.append("〚"); // U+301A // Transform the char to hex notation. Note that we have ensure that c is <= 255 // here, hence only two hexadecimal places are ok. String hex = String.format("%02X", (int) c); sb.append(hex); sb.append("〛"); // U+301B } } } return sb.toString(); } private static boolean isLdhOrMaybeUnderscore(char c, boolean underscore) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || (underscore && c == '_') ; } private static boolean consistsOnlyOfLdhAndMaybeUnderscore(String string, boolean underscore) { for (int i = 0; i < string.length(); i++) { char c = string.charAt(i); if (isLdhOrMaybeUnderscore(c, underscore)) { continue; } return false; } return true; } public static boolean consistsOnlyOfLettersDigitsAndHypen(String string) { return consistsOnlyOfLdhAndMaybeUnderscore(string, false); } public static boolean consistsOnlyOfLettersDigitsHypenAndUnderscore(String string) { return consistsOnlyOfLdhAndMaybeUnderscore(string, true); } public static class LabelToLongException extends IllegalArgumentException { /** * */ private static final long serialVersionUID = 1L; public final String label; LabelToLongException(String label) { this.label = label; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy