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

com.unboundid.ldap.sdk.DNEscapingStrategy Maven / Gradle / Ivy

/*
 * Copyright 2021-2022 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2021-2022 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2021-2022 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk;



import java.io.Serializable;

import com.unboundid.util.ByteString;
import com.unboundid.util.ByteStringBuffer;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;



/**
 * This class defines a set of properties that can be used to indicate which
 * types of optional escaping should be performed by the LDAP SDK when
 * constructing the string representation of DNs and RDNs.
 */
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class DNEscapingStrategy
       implements Serializable
{
  /**
   * A DN escaping strategy that represents a default, user-friendly
   * configuration.  This includes:
   * 
    *
  • * ASCII control characters will be escaped. *
  • *
  • * Displayable non-ASCII characters will not be escaped. *
  • *
  • * Non-displayable non-ASCII characters will be escaped. *
  • *
  • * In non-UTF-8 data, all bytes with the most significant bit set will be * escaped. *
  • *
*/ @NotNull public static final DNEscapingStrategy DEFAULT = new DNEscapingStrategy(true, false, true, true); /** * A DN escaping strategy that indicates that the LDAP SDK should only perform * required escaping and should not perform any optional escaping. */ @NotNull public static final DNEscapingStrategy MINIMAL = new DNEscapingStrategy(false, false, false, false); /** * A base64-encoding strategy that indicates that the LDAP SDK should * perform the maximum amount of DN escaping that is considered reasonable. * All ASCII control characters, all non-ASCII characters and non-UTF-8 bytes * will be escaped. */ @NotNull public static final DNEscapingStrategy MAXIMAL = new DNEscapingStrategy(true, true, true, true); /** * The serial version UID for this serializable class. */ private static final long serialVersionUID = -5438646712027992419L; // Indicates whether ASCII control characters should be escaped. private final boolean escapeASCIIControlCharacters; // Indicates whether displayable non-ASCII characters should be escaped. private final boolean escapeDisplayableNonASCIICharacters; // Indicates whether non-displayable non-ASCII characters should be escaped. private final boolean escapeNonDisplayableNonASCIICharacters; // Indicates whether bytes with the most significant bit set in non-UTF-8 data // should be escaped. private final boolean escapeNonUTF8Data; /** * Creates a new DN escaping strategy with the specified settings. * * @param escapeASCIIControlCharacters * Indicates whether ASCII control characters (characters whose * Unicode code point is less than or equal to 0x1F, or is equal * to 0x7F) should be escaped. Note that the ASCII NULL control * character (0x00) will always be escaped. * @param escapeDisplayableNonASCIICharacters * Indicates whether non-ASCII characters (characters whose * Unicode code point is greater than 0x7F) that are believed to * be displayable (as determined by the * {@link StaticUtils#isLikelyDisplayableCharacter} method) * should be escaped. * @param escapeNonDisplayableNonASCIICharacters * Indicates whether non-ASCII characters (characters whose * Unicode code point is greater than 0x7F) that are not believed * to be displayable (as determined by the * {@link StaticUtils#isLikelyDisplayableCharacter} method) * should be escaped. * @param escapeNonUTF8Data * Indicates whether bytes with the most significant bit set in * non-UTF-8 data should be escaped. Note that if a value does * not represent a valid UTF-8 string, then the * {@code escapeDisplayableNonASCIICharacters} and * {@code escapeNonDisplayableNonASCIICharacters} arguments will * not be used. */ public DNEscapingStrategy(final boolean escapeASCIIControlCharacters, final boolean escapeDisplayableNonASCIICharacters, final boolean escapeNonDisplayableNonASCIICharacters, final boolean escapeNonUTF8Data) { this.escapeASCIIControlCharacters = escapeASCIIControlCharacters; this.escapeDisplayableNonASCIICharacters = escapeDisplayableNonASCIICharacters; this.escapeNonDisplayableNonASCIICharacters = escapeNonDisplayableNonASCIICharacters; this.escapeNonUTF8Data = escapeNonUTF8Data; } /** * Indicates whether ASCII control characters should be escaped. Note that * the ASCII NULL control character (0x00) will always be escaped. * * @return {@code true} if ASCII control characters should be escaped, or * {@code false} if not. */ public boolean escapeASCIIControlCharacters() { return escapeASCIIControlCharacters; } /** * Indicates whether displayable non-ASCII characters (as determined by the * {@link StaticUtils#isLikelyDisplayableCharacter} method) should be escaped. * Note that this only applies to values that represent valid UTF-8 strings. * Values that are not valid UTF-8 strings will use the setting represented * by the {@link #escapeNonUTF8Data} method. * * @return {@code true} if displayable non-ASCII characters should be * escaped, or {@code false} if not. */ public boolean escapeDisplayableNonASCIICharacters() { return escapeDisplayableNonASCIICharacters; } /** * Indicates whether non-displayable non-ASCII characters (as determined by * the {@link StaticUtils#isLikelyDisplayableCharacter} method) should be * escaped. Note that this only applies to values that represent valid UTF-8 * strings. Values that are not valid UTF-8 strings will use the setting * represented by the {@link #escapeNonUTF8Data} method. * * @return {@code true} if non-displayable non-ASCII characters should be * escaped, or {@code false} if not. */ public boolean escapeNonDisplayableNonASCIICharacters() { return escapeNonDisplayableNonASCIICharacters; } /** * Indicates whether bytes with the most significant bit set in non-UTF-8 data * (as determined by the {@link StaticUtils#isValidUTF8} method) should be * escaped. * * @return {@code true} if bytes with the most significant bit set in * non-UTF-8 data should be escaped, or {@code false} if not. */ public boolean escapeNonUTF8Data() { return escapeNonUTF8Data; } /** * Appends an appropriately escaped representation of the provided value to * the given buffer. * * @param value The value to be appended. It must not be {@code null}. * @param buffer The buffer to which the escaped value should be appended. * It must not be {@code null}. */ public void escape(@NotNull final byte[] value, @NotNull final ByteStringBuffer buffer) { // If the value is empty, then we don't need to do anything. final int valueLength = value.length; if ((value == null) || (valueLength == 0)) { return; } // Iterate through the value and examine each byte. Boolean isNonUTF8 = null; for (int i=0; i < valueLength; i++) { final byte b = value[i]; switch (b) { // The following characters will always be escaped anywhere in a value. case '"': case '+': case ',': case ';': case '<': case '>': case '\\': buffer.append('\\'); buffer.append(b); break; // The ASCII NULL character must also always be escaped, but it should // use a hex encoding. case '\u0000': buffer.append("\\00"); break; // Spaces will only be escaped if they are the first or last character // of the value. case ' ': if ((i == 0) || (i == (valueLength - 1))) { buffer.append('\\'); } buffer.append(b); break; // The octothorpe character will only be escaped if it is the first // character of a value. case '#': if (i == 0) { buffer.append('\\'); } buffer.append(b); break; default: // If the byte is between 0x00 and 0x1F (inclusive), or if it's 0x7F, // then it's an ASCII control character. Handle that appropriately. if (((b >= 0x00) && (b <= 0x1F)) || (b == 0x07F)) { if (escapeASCIIControlCharacters) { buffer.append('\\'); buffer.append(StaticUtils.toHex(b)); } else { buffer.append(b); } } // Because Java represents bytes as signed values, if a byte is // greater than zero, then it's an ASCII byte and we won't escape it. else if (b > 0x00) { buffer.append(b); } // If we've gotten here, then the byte is negative, which means that // it's not ASCII. If we know that it's non-UTF-8 data, then handle // that in accordance with the escapeNonUTF8Data flag. Otherwise, // check to see whether it is valid UTF-8 and handle it as either a // string comprised of code points or as non-UTF-8 data. else { if (isNonUTF8 == null) { final byte[] remainingValueBytes = new byte[valueLength - i]; System.arraycopy(value, i, remainingValueBytes, 0, remainingValueBytes.length); if (StaticUtils.isValidUTF8(remainingValueBytes)) { escape(StaticUtils.toUTF8String(remainingValueBytes), buffer, (i == 0)); return; } else { isNonUTF8 = Boolean.TRUE; } } // If we've gotten here, then we know that it's non-UTF-8 data // (because we would have gone to a different method if it was // valid UTF-8), so handle that in accordance with the // escapeNonUTF8Data flag. if (escapeNonUTF8Data) { buffer.append('\\'); buffer.append(StaticUtils.toHex(b)); } else { buffer.append(b); } } break; } } } /** * Appends an appropriately escaped representation of the provided value to * the given buffer. * * @param value The value to be appended. It must not be {@code null}. * @param buffer The buffer to which the escaped value should be appended. * It must not be {@code null}. */ public void escape(@NotNull final String value, @NotNull final ByteStringBuffer buffer) { escape(value, buffer, true); } /** * Appends an appropriately escaped representation of the provided value to * the given buffer. * * @param value The value to be appended. It must not be {@code null}. * @param buffer The buffer to which the escaped value should be appended. * It must not be {@code null}. */ public void escape(@NotNull final ByteString value, @NotNull final ByteStringBuffer buffer) { escape(value.getValue(), buffer); } /** * Appends an appropriately escaped representation of the provided value to * the given buffer. * * @param value The value to be appended. It must not be * {@code null}. * @param buffer The buffer to which the escaped value should be * appended. It must not be {@code null}. * @param isWholeString Indicates whether the provided string represents the * entire value being processed, or if a portion of the * value may have already been processed. */ private void escape(@NotNull final String value, @NotNull final ByteStringBuffer buffer, final boolean isWholeString) { if ((value == null) || value.isEmpty()) { return; } int pos = 0; while (pos < value.length()) { final int codePoint = value.codePointAt(pos); switch (codePoint) { // The following characters will always be escaped anywhere in a value. case '"': case '+': case ',': case ';': case '<': case '>': case '\\': buffer.append('\\'); buffer.append((byte) codePoint); break; // The ASCII NULL character must also always be escaped, but it should // use a hex encoding. case '\u0000': buffer.append("\\00"); break; // Spaces will only be escaped if they are the first or last character // of the value. case ' ': if (((pos == 0) && isWholeString) || (pos == (value.length() - 1))) { buffer.append('\\'); } buffer.append(' '); break; // The octothorpe character will only be escaped if it is the first // character of a value. case '#': if ((pos == 0) && isWholeString) { buffer.append('\\'); } buffer.append('#'); break; default: // If the code point is between 0x00 and 0x1F (inclusive), or if it is // 0x7F, then it's an ASCII control character. Handle that // appropriately. if (((codePoint >= 0x00) && (codePoint <= 0x1F)) || (codePoint == 0x7F)) { final byte codePointByte = (byte) codePoint; if (escapeASCIIControlCharacters) { buffer.append('\\'); buffer.append(StaticUtils.toHex(codePointByte)); } else { buffer.append(codePointByte); } } // If the code point is less than 0x7F, then it's an ASCII character // that we don't need to escape. else if (codePoint < 0x7F) { buffer.append((byte) codePoint); } // If we've gotten here, then the code point must represent a // non-ASCII character. Determine whether it's displayable and handle // it appropriately. else { final String codePointString = new String(new int[] { codePoint }, 0, 1); final byte[] codePointBytes = StaticUtils.getBytes(codePointString); if (StaticUtils.isLikelyDisplayableCharacter(codePoint)) { if (escapeDisplayableNonASCIICharacters) { for (final byte b : codePointBytes) { buffer.append('\\'); buffer.append(StaticUtils.toHex(b)); } } else { buffer.append(codePointBytes); } } else { if (escapeNonDisplayableNonASCIICharacters) { for (final byte b : codePointBytes) { buffer.append('\\'); buffer.append(StaticUtils.toHex(b)); } } else { buffer.append(codePointBytes); } } } break; } final int charsPerCodePoint = Character.charCount(codePoint); pos += charsPerCodePoint; } } /** * Retrieves a string representation of this base64 encoding strategy. * * @return A string representation of this base64 encoding strategy. */ @Override() @NotNull() public String toString() { final StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); } /** * Appends a string representation of this base64 encoding strategy to the * provided buffer. * * @param buffer The buffer to which the string representation should be * appended. */ public void toString(@NotNull final StringBuilder buffer) { buffer.append("DNEscapingStrategy(escapeASCIIControlCharacters="); buffer.append(escapeASCIIControlCharacters); buffer.append(", escapeDisplayableNonASCIICharacters="); buffer.append(escapeDisplayableNonASCIICharacters); buffer.append(", escapeNonDisplayableNonASCIICharacters="); buffer.append(escapeNonDisplayableNonASCIICharacters); buffer.append(", escapeNonUTF8Data="); buffer.append(escapeNonUTF8Data); buffer.append(')'); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy