blob: fe63e35d8c0d450f66bf43388597af655a7c5560 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* https://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 org.apache.commons.xml;
import java.io.InputStream;
import java.io.Reader;
import javax.xml.stream.EventFilter;
import javax.xml.stream.StreamFilter;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLReporter;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.XMLEventAllocator;
import javax.xml.transform.Source;
/**
* {@link XMLInputFactory} wrapper that keeps the {@link Resolvers.FallbackDenyXMLResolver} floors {@link StaxHardener} installs on the entity-resolution hooks
* non-removable by the caller.
*
* <p>Every resolver-valued entry point ({@link #setXMLResolver(XMLResolver)}, {@code setProperty(XMLInputFactory.RESOLVER, ...)} and the Woodstox
* {@code com.ctc.wstx.*Resolver} keys) is routed uniformly: a caller who supplies their own {@link Resolvers.FallbackDenyXMLResolver} takes control and it is
* passed straight to the delegate; otherwise the current resolver on that hook is read, and if it is one of our floors the caller's resolver is set as its
* {@link Resolvers.FallbackDenyXMLResolver#setDelegate delegate} (an opt-in the floor cannot be removed by), or, if the hook is empty, the caller's resolver is
* wrapped in a fresh floor. This matters because Woodstox does not chain resolvers: when a resolver returns {@code null}, {@code DefaultInputResolver} falls
* through to fetching the systemId URL itself, so a caller-set resolver that returns {@code null} must still land behind the floor. {@link #getXMLResolver()} and
* {@code getProperty} report the caller's resolver unwrapped.</p>
*/
final class HardeningXMLInputFactory extends XMLInputFactory {
private final XMLInputFactory delegate;
HardeningXMLInputFactory(final XMLInputFactory delegate) {
this.delegate = delegate;
}
@Override
public void setXMLResolver(final XMLResolver resolver) {
setResolverProperty(XMLInputFactory.RESOLVER, resolver);
}
@Override
public XMLResolver getXMLResolver() {
return unwrap(delegate.getXMLResolver());
}
@Override
public void setProperty(final String name, final Object value) {
// If a resolver property has a value of the wrong type, pass it to the delegate to generate an appropriate exception.
if (isResolverProperty(name) && (value == null || value instanceof XMLResolver)) {
setResolverProperty(name, (XMLResolver) value);
} else {
delegate.setProperty(name, value);
}
}
@Override
public Object getProperty(final String name) {
if (isResolverProperty(name)) {
return unwrap((XMLResolver) delegate.getProperty(name));
}
return delegate.getProperty(name);
}
/**
* Routes a caller-set resolver for the property {@code name} behind the floor currently installed on that hook.
*
* @param name The resolver-valued property being set.
* @param resolver The caller's resolver, or their own {@link Resolvers.FallbackDenyXMLResolver} to take control.
*/
private void setResolverProperty(final String name, final XMLResolver resolver) {
if (resolver instanceof Resolvers.FallbackDenyXMLResolver) {
// The caller supplies their own floor: hand it to the delegate as-is.
delegate.setProperty(name, resolver);
} else {
final Object current = delegate.getProperty(name);
if (current instanceof Resolvers.FallbackDenyXMLResolver) {
((Resolvers.FallbackDenyXMLResolver) current).setDelegate(resolver);
} else {
delegate.setProperty(name, new Resolvers.FallbackDenyXMLResolver(resolver));
}
}
}
private static boolean isResolverProperty(final String name) {
return XMLInputFactory.RESOLVER.equals(name)
|| StaxHardener.WSTX_DTD_RESOLVER.equals(name)
|| StaxHardener.WSTX_ENTITY_RESOLVER.equals(name)
|| StaxHardener.WSTX_UNDECLARED_ENTITY_RESOLVER.equals(name);
}
private static XMLResolver unwrap(final XMLResolver resolver) {
return resolver instanceof Resolvers.FallbackDenyXMLResolver ? ((Resolvers.FallbackDenyXMLResolver) resolver).getDelegate() : resolver;
}
// <editor-fold defaultstate="collapsed" desc="Trivial delegation">
@Override
public XMLStreamReader createXMLStreamReader(final Reader reader) throws XMLStreamException {
return delegate.createXMLStreamReader(reader);
}
@Override
public XMLStreamReader createXMLStreamReader(final Source source) throws XMLStreamException {
return delegate.createXMLStreamReader(source);
}
@Override
public XMLStreamReader createXMLStreamReader(final InputStream stream) throws XMLStreamException {
return delegate.createXMLStreamReader(stream);
}
@Override
public XMLStreamReader createXMLStreamReader(final InputStream stream, final String encoding) throws XMLStreamException {
return delegate.createXMLStreamReader(stream, encoding);
}
@Override
public XMLStreamReader createXMLStreamReader(final String systemId, final InputStream stream) throws XMLStreamException {
return delegate.createXMLStreamReader(systemId, stream);
}
@Override
public XMLStreamReader createXMLStreamReader(final String systemId, final Reader reader) throws XMLStreamException {
return delegate.createXMLStreamReader(systemId, reader);
}
@Override
public XMLEventReader createXMLEventReader(final Reader reader) throws XMLStreamException {
return delegate.createXMLEventReader(reader);
}
@Override
public XMLEventReader createXMLEventReader(final String systemId, final Reader reader) throws XMLStreamException {
return delegate.createXMLEventReader(systemId, reader);
}
@Override
public XMLEventReader createXMLEventReader(final XMLStreamReader reader) throws XMLStreamException {
return delegate.createXMLEventReader(reader);
}
@Override
public XMLEventReader createXMLEventReader(final Source source) throws XMLStreamException {
return delegate.createXMLEventReader(source);
}
@Override
public XMLEventReader createXMLEventReader(final InputStream stream) throws XMLStreamException {
return delegate.createXMLEventReader(stream);
}
@Override
public XMLEventReader createXMLEventReader(final InputStream stream, final String encoding) throws XMLStreamException {
return delegate.createXMLEventReader(stream, encoding);
}
@Override
public XMLEventReader createXMLEventReader(final String systemId, final InputStream stream) throws XMLStreamException {
return delegate.createXMLEventReader(systemId, stream);
}
@Override
public XMLStreamReader createFilteredReader(final XMLStreamReader reader, final StreamFilter filter) throws XMLStreamException {
return delegate.createFilteredReader(reader, filter);
}
@Override
public XMLEventReader createFilteredReader(final XMLEventReader reader, final EventFilter filter) throws XMLStreamException {
return delegate.createFilteredReader(reader, filter);
}
@Override
public XMLReporter getXMLReporter() {
return delegate.getXMLReporter();
}
@Override
public void setXMLReporter(final XMLReporter reporter) {
delegate.setXMLReporter(reporter);
}
@Override
public boolean isPropertySupported(final String name) {
return delegate.isPropertySupported(name);
}
@Override
public void setEventAllocator(final XMLEventAllocator allocator) {
delegate.setEventAllocator(allocator);
}
@Override
public XMLEventAllocator getEventAllocator() {
return delegate.getEventAllocator();
}
// </editor-fold>
}