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

com.github.robtimus.obfuscation.xml.XMLObfuscator Maven / Gradle / Ivy

The newest version!
/*
 * XMLObfuscator.java
 * Copyright 2020 Rob Spoor
 *
 * 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.
 */

package com.github.robtimus.obfuscation.xml;

import static com.github.robtimus.obfuscation.support.CaseSensitivity.CASE_SENSITIVE;
import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.appendAtMost;
import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.checkStartAndEnd;
import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.copyTo;
import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.counting;
import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.discardAll;
import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.reader;
import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.writer;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.codehaus.stax2.XMLOutputFactory2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ctc.wstx.api.WstxInputProperties;
import com.ctc.wstx.api.WstxOutputProperties;
import com.ctc.wstx.exc.WstxLazyException;
import com.ctc.wstx.stax.WstxInputFactory;
import com.ctc.wstx.stax.WstxOutputFactory;
import com.github.robtimus.obfuscation.Obfuscator;
import com.github.robtimus.obfuscation.support.CachingObfuscatingWriter;
import com.github.robtimus.obfuscation.support.CaseSensitivity;
import com.github.robtimus.obfuscation.support.CountingReader;
import com.github.robtimus.obfuscation.support.LimitAppendable;
import com.github.robtimus.obfuscation.support.MapBuilder;
import com.github.robtimus.obfuscation.xml.XMLObfuscator.ElementConfigurer.ObfuscationMode;

/**
 * An obfuscator that obfuscates XML elements in {@link CharSequence CharSequences} or the contents of {@link Reader Readers}.
 * An {@code XMLObfuscator} will only obfuscate text, and ignore leading and trailing whitespace.
 * It will never obfuscate element tag names, comments, etc.
 * 

* By default, if an obfuscator is configured for an element, it will be used to obfuscate the text of all nested elements as well. This can be turned * off using {@link Builder#excludeNestedElementsByDefault()} and/or {@link ElementConfigurer#excludeNestedElements()}. This will allow the nested * elements to use their own obfuscators. *

* Note: preferably, obfuscation is done in such a way that the original structure and formatting is maintained. However, some functionality does not * allow for this to happen. If this functionality is needed, obfuscation will instead generate new, obfuscated XML documents. The resulting * obfuscated XML documents may slightly differ from the original. Methods in class {@link Builder} will mention it if they result in generating new * XML documents. * * @author Rob Spoor */ public final class XMLObfuscator extends Obfuscator { private static final Logger LOGGER = LoggerFactory.getLogger(XMLObfuscator.class); private static final XMLInputFactory INPUT_FACTORY = createInputFactory(); private static final XMLOutputFactory OUTPUT_FACTORY = createOutputFactory(); private final Map elements; private final Map qualifiedElements; private final Map attributes; private final Map qualifiedAttributes; private final String malformedXMLWarning; private final long limit; private final String truncatedIndicator; private final boolean generateXML; private XMLObfuscator(ObfuscatorBuilder builder) { elements = builder.elements(); qualifiedElements = builder.qualifiedElements(); attributes = builder.attributes(); qualifiedAttributes = builder.qualifiedAttributes(); malformedXMLWarning = builder.malformedXMLWarning; limit = builder.limit; truncatedIndicator = builder.truncatedIndicator; generateXML = builder.generateXML; } static XMLInputFactory createInputFactory() { // Explicitly use Woodstox; any other implementation may not produce the correct locations XMLInputFactory inputFactory = new WstxInputFactory(); setPropertyIfSupported(inputFactory, XMLConstants.ACCESS_EXTERNAL_DTD, ""); //$NON-NLS-1$ setPropertyIfSupported(inputFactory, XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); //$NON-NLS-1$ setPropertyIfSupported(inputFactory, XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); //$NON-NLS-1$ setPropertyIfSupported(inputFactory, XMLConstants.FEATURE_SECURE_PROCESSING, true); setPropertyIfSupported(inputFactory, XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); setPropertyIfSupported(inputFactory, WstxInputProperties.P_DTD_RESOLVER, (XMLResolver) (publicID, systemID, baseURI, namespace) -> { throw new XMLStreamException(Messages.XMLObfuscator.externalDTDsNotSupported(systemID)); }); return inputFactory; } private static void setPropertyIfSupported(XMLInputFactory inputFactory, String name, Object value) { if (inputFactory.isPropertySupported(name)) { inputFactory.setProperty(name, value); } else { String message = Messages.XMLObfuscator.unsupportedProperty(name); LOGGER.warn(message); } } private static XMLOutputFactory createOutputFactory() { // Explicitly use Woodstox, to be consistent with the input factory XMLOutputFactory2 outputFactory = new WstxOutputFactory(); outputFactory.configureForSpeed(); // Needed to get namespace declarations in the resulting XML outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); outputFactory.setProperty(WstxOutputProperties.P_USE_DOUBLE_QUOTES_IN_XML_DECL, true); return outputFactory; } @Override public CharSequence obfuscateText(CharSequence s, int start, int end) { checkStartAndEnd(s, start, end); StringBuilder sb = new StringBuilder(end - start); obfuscateText(s, start, end, sb); return sb.toString(); } @Override public void obfuscateText(CharSequence s, int start, int end, Appendable destination) throws IOException { checkStartAndEnd(s, start, end); if (generateXML) { obfuscateTextWriting(s, start, end, destination); } else { obfuscateTextIndexed(s, start, end, destination); } } @Override public void obfuscateText(Reader input, Appendable destination) throws IOException { if (generateXML) { obfuscateTextWriting(input, destination); } else { obfuscateTextIndexed(input, destination); } } private void obfuscateTextWriting(CharSequence s, int start, int end, Appendable destination) throws IOException { @SuppressWarnings("resource") Reader reader = reader(s, start, end); LimitAppendable appendable = appendAtMost(destination, limit); // No need to consume the reader, as it's backed by the CharSequence obfuscateTextWriting(reader, appendable, false); if (appendable.limitExceeded() && truncatedIndicator != null) { destination.append(String.format(truncatedIndicator, end - start)); } } private void obfuscateTextWriting(Reader input, Appendable destination) throws IOException { @SuppressWarnings("resource") CountingReader countingReader = counting(input); LimitAppendable appendable = appendAtMost(destination, limit); // Consume the reader so countingReader.count() will give the correct result obfuscateTextWriting(countingReader, appendable, true); if (appendable.limitExceeded() && truncatedIndicator != null) { destination.append(String.format(truncatedIndicator, countingReader.count())); } } private void obfuscateTextWriting(Reader reader, LimitAppendable destination, boolean consumeReader) throws IOException { try { WritingObfuscatingXMLParser parser = createWritingParser(reader, destination); parser.initialize(); try { while (parser.hasNext() && !destination.limitExceeded()) { parser.processNext(); } } finally { parser.flush(); } if (consumeReader) { discardAll(reader); } } catch (XMLStreamException | WstxLazyException e) { LOGGER.warn(Messages.XMLObfuscator.malformedXML.warning(), e); if (malformedXMLWarning != null) { destination.append(malformedXMLWarning); } } } private WritingObfuscatingXMLParser createWritingParser(Reader input, LimitAppendable destination) { XMLStreamReader xmlStreamReader = createXmlStreamReader(input); XMLStreamWriter xmlStreamWriter = createXmlStreamWriter(destination); return new WritingObfuscatingXMLParser(xmlStreamReader, xmlStreamWriter, elements, qualifiedElements, attributes, qualifiedAttributes); } private void obfuscateTextIndexed(CharSequence s, int start, int end, Appendable destination) throws IOException { @SuppressWarnings("resource") Reader reader = reader(s, start, end); LimitAppendable appendable = appendAtMost(destination, limit); obfuscateTextIndexed(reader, new Source.OfCharSequence(s), start, end, appendable); if (appendable.limitExceeded() && truncatedIndicator != null) { destination.append(String.format(truncatedIndicator, end - start)); } } private void obfuscateTextIndexed(Reader input, Appendable destination) throws IOException { @SuppressWarnings("resource") CountingReader countingReader = counting(input); Source.OfReader source = new Source.OfReader(countingReader, LOGGER); @SuppressWarnings("resource") Reader reader = copyTo(countingReader, source); LimitAppendable appendable = appendAtMost(destination, limit); obfuscateTextIndexed(reader, source, 0, -1, appendable); if (appendable.limitExceeded() && truncatedIndicator != null) { destination.append(String.format(truncatedIndicator, countingReader.count())); } } private void obfuscateTextIndexed(Reader input, Source source, int start, int end, LimitAppendable destination) throws IOException { IndexedObfuscatingXMLParser parser = createIndexedParser(input, source, start, end, destination); try { while (parser.hasNext() && !destination.limitExceeded()) { parser.processNext(); } parser.appendRemainder(); } catch (XMLStreamException | WstxLazyException e) { LOGGER.warn(Messages.XMLObfuscator.malformedXML.warning(), e); parser.finishLatestText(); if (malformedXMLWarning != null) { destination.append(malformedXMLWarning); } } } private IndexedObfuscatingXMLParser createIndexedParser(Reader input, Source source, int start, int end, LimitAppendable destination) { XMLStreamReader xmlStreamReader = createXmlStreamReader(input); return new IndexedObfuscatingXMLParser(xmlStreamReader, source, start, end, destination, elements, qualifiedElements); } private XMLStreamReader createXmlStreamReader(Reader input) { try { return INPUT_FACTORY.createXMLStreamReader(input); } catch (XMLStreamException e) { throw new IllegalStateException(e); } } @SuppressWarnings("resource") private XMLStreamWriter createXmlStreamWriter(Appendable destination) { try { return OUTPUT_FACTORY.createXMLStreamWriter(writer(destination)); } catch (XMLStreamException e) { throw new IllegalStateException(e); } } @Override public Writer streamTo(Appendable destination) { return new CachingObfuscatingWriter(this, destination); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || o.getClass() != getClass()) { return false; } XMLObfuscator other = (XMLObfuscator) o; return elements.equals(other.elements) && qualifiedElements.equals(other.qualifiedElements) && attributes.equals(other.attributes) && qualifiedAttributes.equals(other.qualifiedAttributes) && Objects.equals(malformedXMLWarning, other.malformedXMLWarning) && limit == other.limit && Objects.equals(truncatedIndicator, other.truncatedIndicator) && generateXML == other.generateXML; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + elements.hashCode(); result = prime * result + qualifiedElements.hashCode(); result = prime * result + attributes.hashCode(); result = prime * result + qualifiedAttributes.hashCode(); result = prime * result + Objects.hashCode(malformedXMLWarning); result = prime * result + Long.hashCode(limit); result = prime * result + Objects.hashCode(truncatedIndicator); result = prime * result + Boolean.hashCode(generateXML); return result; } @Override @SuppressWarnings("nls") public String toString() { return getClass().getName() + "[elements=" + elements + ",qualifiedElements=" + qualifiedElements + ",attributes=" + attributes + ",qualifiedAttributes=" + qualifiedAttributes + ",malformedXMLWarning=" + malformedXMLWarning + ",limit=" + limit + ",truncatedIndicator=" + truncatedIndicator + ",generateXML=" + generateXML + "]"; } /** * Returns a builder that will create {@code XMLObfuscators}. * * @return A builder that will create {@code XMLObfuscators}. */ public static Builder builder() { return new ObfuscatorBuilder(); } /** * A builder for {@link XMLObfuscator XMLObfuscators}. * * @author Rob Spoor */ public interface Builder { /** * Adds an element to obfuscate. *

* This method is an alias for {@link #withElement(String, Obfuscator, CaseSensitivity)} with the last specified default case sensitivity * using {@link #caseSensitiveByDefault()} or {@link #caseInsensitiveByDefault()}. The default is {@link CaseSensitivity#CASE_SENSITIVE}. * * @param element The local name of the element. * @param obfuscator The obfuscator to use for obfuscating the element. * @return An object that can be used to configure the element, or continue building {@link XMLObfuscator XMLObfuscators}. * @throws NullPointerException If the given element name or obfuscator is {@code null}. * @throws IllegalArgumentException If an element with the same local name and the same case sensitivity was already added. */ ElementConfigurer withElement(String element, Obfuscator obfuscator); /** * Adds an element to obfuscate. * * @param element The local name of the element. * @param obfuscator The obfuscator to use for obfuscating the element. * @param caseSensitivity The case sensitivity for the element. * @return An object that can be used to configure the element, or continue building {@link XMLObfuscator XMLObfuscators}. * @throws NullPointerException If the given element name, obfuscator or case sensitivity is {@code null}. * @throws IllegalArgumentException If an element with the same local name and the same case sensitivity was already added. */ ElementConfigurer withElement(String element, Obfuscator obfuscator, CaseSensitivity caseSensitivity); /** * Adds an element to obfuscate. * Any element added using this method will take precedence over elements added using {@link #withElement(String, Obfuscator)} or * {@link #withElement(String, Obfuscator, CaseSensitivity)}. * * @param element The qualified name of the element. * @param obfuscator The obfuscator to use for obfuscating the element. * @return An object that can be used to configure the element, or continue building {@link XMLObfuscator XMLObfuscators}. * @throws NullPointerException If the given element name or obfuscator is {@code null}. * @throws IllegalArgumentException If an element with the same qualified name was already added. */ ElementConfigurer withElement(QName element, Obfuscator obfuscator); /** * Adds an attribute to obfuscate. This will cause any occurrence of the attribute to be obfuscated, regardless of their elements. * The returned object can be used to define obfuscators for occurrences of the attribute in specific elements. *

* This method is an alias for {@link #withAttribute(String, Obfuscator, CaseSensitivity)} with the last specified default case sensitivity * using {@link #caseSensitiveByDefault()} or {@link #caseInsensitiveByDefault()}. The default is {@link CaseSensitivity#CASE_SENSITIVE}. *

* Note: because locations of attributes are not easily available, XML obfuscators will generate new, obfuscated documents when attributes * need to be obfuscated. * * @param attribute The local name of the attribute. * @param obfuscator The obfuscator to use for obfuscating the attribute. * @return An object that can be used to configure the attribute, or continue building {@link XMLObfuscator XMLObfuscators}. * @throws NullPointerException If the given attribute name or obfuscator is {@code null}. * @throws IllegalArgumentException If an attribute with the same local name and the same case sensitivity was already added. * @since 1.2 */ AttributeConfigurer withAttribute(String attribute, Obfuscator obfuscator); /** * Adds an attribute to obfuscate. This will cause any occurrence of the attribute to be obfuscated, regardless of their elements. * The returned object can be used to define obfuscators for occurrences of the attribute in specific elements. *

* Note: because locations of attributes are not easily available, XML obfuscators will generate new, obfuscated documents when attributes * need to be obfuscated. * * @param attribute The local name of the attribute. * @param obfuscator The obfuscator to use for obfuscating the attribute. * @param caseSensitivity The case sensitivity for the attribute. * @return An object that can be used to configure the attribute, or continue building {@link XMLObfuscator XMLObfuscators}. * @throws NullPointerException If the given attribute name, obfuscator or case sensitivity is {@code null}. * @throws IllegalArgumentException If an attribute with the same local name and the same case sensitivity was already added. * @since 1.2 */ AttributeConfigurer withAttribute(String attribute, Obfuscator obfuscator, CaseSensitivity caseSensitivity); /** * Adds an attribute to obfuscate. This will cause any occurrence of the attribute to be obfuscated, regardless of their elements. * The returned object can be used to define obfuscators for occurrences of the attribute in specific elements. * Any attribute added using this method will take precedence over attributes added using {@link #withAttribute(String, Obfuscator)} or * {@link #withAttribute(String, Obfuscator, CaseSensitivity)}. *

* Note: because locations of attributes are not easily available, XML obfuscators will generate new, obfuscated documents when attributes * need to be obfuscated. * * @param attribute The qualified name of the attribute. * @param obfuscator The obfuscator to use for obfuscating the attribute. * @return An object that can be used to configure the attribute, or continue building {@link XMLObfuscator XMLObfuscators}. * @throws NullPointerException If the given attribute name or obfuscator is {@code null}. * @throws IllegalArgumentException If an attribute with the same qualified name was already added. * @since 1.2 */ AttributeConfigurer withAttribute(QName attribute, Obfuscator obfuscator); /** * Sets the default case sensitivity for new elements and attributes to {@link CaseSensitivity#CASE_SENSITIVE}. This is the default setting. *

* Note that this will not change the case sensitivity of any element or attribute that was already added. * * @return This object. */ Builder caseSensitiveByDefault(); /** * Sets the default case sensitivity for new elements and attributes to {@link CaseSensitivity#CASE_INSENSITIVE}. *

* Note that this will not change the case sensitivity of any element or attribute that was already added. * * @return This object. */ Builder caseInsensitiveByDefault(); /** * Indicates that by default nested elements will not be obfuscated. * This method is shorthand for calling {@link #excludeNestedElementsByDefault()}. *

* Note that this will not change what will be obfuscated for any element that was already added. * * @return This object. */ default Builder textOnlyByDefault() { return excludeNestedElementsByDefault(); } /** * Indicates that by default nested elements will not be obfuscated. * This method is an alias for {@link #forNestedElementsByDefault(ObfuscationMode)} in combination with {@link ObfuscationMode#EXCLUDE}. *

* Note that this will not change what will be obfuscated for any element that was already added. * * @return This object. */ default Builder excludeNestedElementsByDefault() { return forNestedElementsByDefault(ObfuscationMode.EXCLUDE); } /** * Indicates that by default nested elements will be obfuscated (default). * This method is shorthand for calling {@link #includeNestedElementsByDefault()}. *

* Note that this will not change what will be obfuscated for any element that was already added. * * @return This object. */ default Builder allByDefault() { return includeNestedElementsByDefault(); } /** * Indicates that by default nested elements will be obfuscated (default). * This method is an alias for {@link #forNestedElementsByDefault(ObfuscationMode)} in combination with {@link ObfuscationMode#INHERIT}. *

* Note that this will not change what will be obfuscated for any element that was already added. * * @return This object. */ default Builder includeNestedElementsByDefault() { return forNestedElementsByDefault(ObfuscationMode.INHERIT); } /** * Indicates how to handle nested elements. The default is {@link ObfuscationMode#INHERIT}. * This can be overridden per element using {@link ElementConfigurer#forNestedElements(ObfuscationMode)} *

* Note that this will not change what will be obfuscated for any property that was already added. * * @param obfuscationMode The obfuscation mode that determines how to handle nested elements. * @return This object. * @throws NullPointerException If the given obfuscation mode is {@code null}. * @since 1.4 */ Builder forNestedElementsByDefault(ObfuscationMode obfuscationMode); /** * Sets the warning to include if an {@link XMLStreamException} is thrown. * This can be used to override the default message. Use {@code null} to omit the warning. * * @param warning The warning to include. * @return This object. */ Builder withMalformedXMLWarning(String warning); /** * Sets the limit for the obfuscated result. * * @param limit The limit to use. * @return An object that can be used to configure the handling when the obfuscated result exceeds a pre-defined limit, * or continue building {@link XMLObfuscator XMLObfuscators}. * @throws IllegalArgumentException If the given limit is negative. * @since 1.1 */ LimitConfigurer limitTo(long limit); /** * Indicates that XML obfuscators will always generate new, obfuscated documents. This method can be called when generating obfuscated * documents is preferred, and no other method triggers the generation of obfuscated documents. * * @return This object. * @since 1.3 */ Builder generateXML(); /** * This method allows the application of a function to this builder. *

* Any exception thrown by the function will be propagated to the caller. * * @param The type of the result of the function. * @param f The function to apply. * @return The result of applying the function to this builder. */ default R transform(Function f) { return f.apply(this); } /** * Creates a new {@code XMLObfuscator} with the elements and obfuscators added to this builder. * * @return The created {@code XMLObfuscator}. */ XMLObfuscator build(); } /** * An object that can be used to configure an element that should be obfuscated. * * @author Rob Spoor */ public interface ElementConfigurer extends Builder { /** * Indicates that elements nested in elements with the current name will not be obfuscated. * This method is shorthand for calling both {@link #excludeNestedElements()}. * * @return This object. */ default ElementConfigurer textOnly() { return excludeNestedElements(); } /** * Indicates that elements nested in elements with the current name will not be obfuscated. * This method is an alias for {@link #forNestedElements(ObfuscationMode)} in combination with {@link ObfuscationMode#EXCLUDE}. * * @return This object. */ default ElementConfigurer excludeNestedElements() { return forNestedElements(ObfuscationMode.EXCLUDE); } /** * Indicates that elements nested in elements with the current name will be obfuscated. * This method is shorthand for calling {@link #includeNestedElements()}. * * @return This object. */ default ElementConfigurer all() { return includeNestedElements(); } /** * Indicates that elements nested in elements with the current name will be obfuscated. * This method is an alias for {@link #forNestedElements(ObfuscationMode)} in combination with {@link ObfuscationMode#INHERIT}. * * @return This object. */ default ElementConfigurer includeNestedElements() { return forNestedElements(ObfuscationMode.INHERIT); } /** * Indicates how to handle nested elements. The default is {@link ObfuscationMode#INHERIT}. * * @param obfuscationMode The obfuscation mode that determines how to handle nested elements. * @return This object. * @throws NullPointerException If the given obfuscation mode is {@code null}. * @since 1.4 */ ElementConfigurer forNestedElements(ObfuscationMode obfuscationMode); /** * The possible ways to deal with nested elements. * * @author Rob Spoor * @since 1.4 */ enum ObfuscationMode { /** Don't obfuscate nested elements, but instead traverse into them. Only the text of the element itself will be obfuscated. **/ EXCLUDE, /** Use the obfuscator for the text of the element itself as well as the text of all nested elements. **/ INHERIT, /** * Use the obfuscator for the text of the element itself as well as the text of all nested elements. * If a nested element has its own obfuscator defined this will be used instead. **/ INHERIT_OVERRIDABLE, } } /** * An object that can be used to configure an attribute that should be obfuscated. * * @author Rob Spoor * @since 1.2 */ public interface AttributeConfigurer extends Builder { /** * Sets the obfuscator to use for occurrences of the attribute for a specific element. *

* This method is an alias for {@link #forElement(String, Obfuscator, CaseSensitivity)} with the last specified default case sensitivity * using {@link #caseSensitiveByDefault()} or {@link #caseInsensitiveByDefault()}. The default is {@link CaseSensitivity#CASE_SENSITIVE}. * * @param element The local name of the element. * @param obfuscator The obfuscator to use for obfuscating the attribute. * @return This object. * @throws NullPointerException If the given element name or obfuscator is {@code null}. * @throws IllegalArgumentException If an element with the same local name and the same case sensitivity was already added for the attribute. * @since 1.3 */ AttributeConfigurer forElement(String element, Obfuscator obfuscator); /** * Sets the obfuscator to use for occurrences of the attribute for a specific element. * * @param element The local name of the element. * @param obfuscator The obfuscator to use for obfuscating the attribute. * @param caseSensitivity The case sensitivity for the element. * @return This object. * @throws NullPointerException If the given element name, obfuscator or case sensitivity is {@code null}. * @throws IllegalArgumentException If an element with the same local name and the same case sensitivity was already added for the attribute. * @since 1.3 */ AttributeConfigurer forElement(String element, Obfuscator obfuscator, CaseSensitivity caseSensitivity); /** * Sets the obfuscator to use for occurrences of the attribute for a specific element. * Any element added using this method will take precedence over elements added using {@link #forElement(String, Obfuscator)} or * {@link #forElement(String, Obfuscator, CaseSensitivity)}. * * @param element The qualified name of the element. * @param obfuscator The obfuscator to use for obfuscating the attribute. * @return This object. * @throws NullPointerException If the given element name or obfuscator is {@code null}. * @throws IllegalArgumentException If an element with the same qualified name was already added for the attribute. * @since 1.3 */ AttributeConfigurer forElement(QName element, Obfuscator obfuscator); } /** * An object that can be used to configure handling when the obfuscated result exceeds a pre-defined limit. * * @author Rob Spoor * @since 1.1 */ public interface LimitConfigurer extends Builder { /** * Sets the indicator to use when the obfuscated result is truncated due to the limit being exceeded. * There can be one place holder for the total number of characters. Defaults to {@code ... (total: %d)}. * Use {@code null} to omit the indicator. * * @param pattern The pattern to use as indicator. * @return This object. */ LimitConfigurer withTruncatedIndicator(String pattern); } private static final class ObfuscatorBuilder implements ElementConfigurer, AttributeConfigurer, LimitConfigurer { private final MapBuilder elements; private final Map qualifiedElements; private final MapBuilder attributes; private final Map qualifiedAttributes; private String malformedXMLWarning; private long limit; private String truncatedIndicator; // default settings private CaseSensitivity defaultCaseSensitivity; private ObfuscationMode forNestedElementsByDefault; // per element / attribute settings private String element; private QName qualifiedElement; private String attribute; private QName qualifiedAttribute; private Obfuscator obfuscator; private CaseSensitivity caseSensitivity; private ObfuscationMode forNestedElements; private MapBuilder attributeElements; private Map qualifiedAttributeElements; // calculated settings private boolean generateXML; private ObfuscatorBuilder() { elements = new MapBuilder<>(); qualifiedElements = new HashMap<>(); attributes = new MapBuilder<>(); qualifiedAttributes = new HashMap<>(); malformedXMLWarning = Messages.XMLObfuscator.malformedXML.text(); limit = Long.MAX_VALUE; truncatedIndicator = "... (total: %d)"; //$NON-NLS-1$ defaultCaseSensitivity = CASE_SENSITIVE; forNestedElementsByDefault = ObfuscationMode.INHERIT; generateXML = false; } @Override public ElementConfigurer withElement(String element, Obfuscator obfuscator) { return withElement(element, obfuscator, defaultCaseSensitivity); } @Override public ElementConfigurer withElement(String element, Obfuscator obfuscator, CaseSensitivity caseSensitivity) { addLastElementOrAttribute(); elements.testEntry(element, caseSensitivity); this.element = element; this.obfuscator = obfuscator; this.caseSensitivity = caseSensitivity; this.forNestedElements = forNestedElementsByDefault; return this; } @Override public ElementConfigurer withElement(QName element, Obfuscator obfuscator) { addLastElementOrAttribute(); Objects.requireNonNull(element); Objects.requireNonNull(obfuscator); if (qualifiedElements.containsKey(element)) { throw new IllegalArgumentException(Messages.XMLObfuscator.duplicateElement(element)); } this.qualifiedElement = element; this.obfuscator = obfuscator; this.caseSensitivity = null; this.forNestedElements = forNestedElementsByDefault; return this; } @Override public AttributeConfigurer withAttribute(String attribute, Obfuscator obfuscator) { return withAttribute(attribute, obfuscator, defaultCaseSensitivity); } @Override public AttributeConfigurer withAttribute(String attribute, Obfuscator obfuscator, CaseSensitivity caseSensitivity) { addLastElementOrAttribute(); attributes.testEntry(attribute, caseSensitivity); this.attribute = attribute; this.obfuscator = obfuscator; this.caseSensitivity = caseSensitivity; this.attributeElements = new MapBuilder<>(); this.qualifiedAttributeElements = new HashMap<>(); generateXML(); return this; } @Override public AttributeConfigurer withAttribute(QName attribute, Obfuscator obfuscator) { addLastElementOrAttribute(); Objects.requireNonNull(attribute); Objects.requireNonNull(obfuscator); if (qualifiedAttributes.containsKey(attribute)) { throw new IllegalArgumentException(Messages.XMLObfuscator.duplicateAttribute(attribute)); } this.qualifiedAttribute = attribute; this.obfuscator = obfuscator; this.caseSensitivity = null; this.attributeElements = new MapBuilder<>(); this.qualifiedAttributeElements = new HashMap<>(); generateXML(); return this; } @Override public AttributeConfigurer forElement(String element, Obfuscator obfuscator) { return forElement(element, obfuscator, defaultCaseSensitivity); } @Override public AttributeConfigurer forElement(String element, Obfuscator obfuscator, CaseSensitivity caseSensitivity) { attributeElements.withEntry(element, obfuscator, caseSensitivity); return this; } @Override public AttributeConfigurer forElement(QName element, Obfuscator obfuscator) { Objects.requireNonNull(element); Objects.requireNonNull(obfuscator); if (qualifiedAttributeElements.containsKey(element)) { throw new IllegalArgumentException(Messages.XMLObfuscator.duplicateElement(element)); } qualifiedAttributeElements.put(element, obfuscator); return this; } @Override public Builder caseSensitiveByDefault() { defaultCaseSensitivity = CASE_SENSITIVE; return this; } @Override public Builder caseInsensitiveByDefault() { defaultCaseSensitivity = CaseSensitivity.CASE_INSENSITIVE; return this; } @Override public Builder forNestedElementsByDefault(ObfuscationMode obfuscationMode) { forNestedElementsByDefault = Objects.requireNonNull(obfuscationMode); return this; } @Override public ElementConfigurer forNestedElements(ObfuscationMode obfuscationMode) { forNestedElements = Objects.requireNonNull(obfuscationMode); return this; } @Override public Builder withMalformedXMLWarning(String warning) { malformedXMLWarning = warning; return this; } @Override public LimitConfigurer limitTo(long limit) { if (limit < 0) { throw new IllegalArgumentException(limit + " < 0"); //$NON-NLS-1$ } this.limit = limit; return this; } @Override public LimitConfigurer withTruncatedIndicator(String pattern) { this.truncatedIndicator = pattern; return this; } @Override public Builder generateXML() { generateXML = true; return this; } private Map elements() { return elements.build(); } private Map qualifiedElements() { return Collections.unmodifiableMap(new HashMap<>(qualifiedElements)); } private Map attributes() { return attributes.build(); } private Map qualifiedAttributes() { return Collections.unmodifiableMap(new HashMap<>(qualifiedAttributes)); } private Map attributeElements() { return attributeElements.build(); } private Map qualifiedAttributeElements() { return Collections.unmodifiableMap(new HashMap<>(qualifiedAttributeElements)); } private void addLastElementOrAttribute() { if (attribute != null) { AttributeConfig attributeConfig = new AttributeConfig(obfuscator, attributeElements(), qualifiedAttributeElements()); attributes.withEntry(attribute, attributeConfig, caseSensitivity); } else if (qualifiedAttribute != null) { AttributeConfig attributeConfig = new AttributeConfig(obfuscator, attributeElements(), qualifiedAttributeElements()); qualifiedAttributes.put(qualifiedAttribute, attributeConfig); } else if (element != null) { ElementConfig elementConfig = new ElementConfig(obfuscator, forNestedElements); elements.withEntry(element, elementConfig, caseSensitivity); } else if (qualifiedElement != null) { ElementConfig elementConfig = new ElementConfig(obfuscator, forNestedElements); qualifiedElements.put(qualifiedElement, elementConfig); } element = null; qualifiedElement = null; attribute = null; qualifiedAttribute = null; obfuscator = null; caseSensitivity = defaultCaseSensitivity; forNestedElements = forNestedElementsByDefault; attributeElements = null; qualifiedAttributeElements = null; } @Override public XMLObfuscator build() { addLastElementOrAttribute(); return new XMLObfuscator(this); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy