| /** |
| * 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 |
| * |
| * 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 org.apache.xml.security.stax.impl; |
| |
| import org.apache.xml.security.exceptions.XMLSecurityException; |
| import org.apache.xml.security.stax.ext.OutputProcessorChain; |
| import org.apache.xml.security.stax.ext.SecurePart; |
| import org.apache.xml.security.stax.ext.XMLSecurityConstants; |
| import org.apache.xml.security.stax.ext.stax.XMLSecAttribute; |
| import org.apache.xml.security.stax.ext.stax.XMLSecEvent; |
| import org.apache.xml.security.stax.ext.stax.XMLSecEventFactory; |
| import org.apache.xml.security.stax.ext.stax.XMLSecNamespace; |
| |
| import javax.xml.XMLConstants; |
| import javax.xml.namespace.NamespaceContext; |
| import javax.xml.namespace.QName; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * Custom XMLStreamWriter to map XMLStreamWriter method calls into XMLEvent's |
| * |
| */ |
| public class XMLSecurityStreamWriter implements XMLStreamWriter { |
| |
| private final OutputProcessorChain outputProcessorChain; |
| private Element elementStack; |
| private Element openStartElement; |
| private NSContext namespaceContext = new NSContext(null); |
| private boolean endDocumentWritten = false; |
| private boolean haveToWriteEndElement = false; |
| private SecurePart signEntireRequestPart; |
| private SecurePart encryptEntireRequestPart; |
| |
| public XMLSecurityStreamWriter(OutputProcessorChain outputProcessorChain) { |
| this.outputProcessorChain = outputProcessorChain; |
| } |
| |
| private void chainProcessEvent(XMLSecEvent xmlSecEvent) throws XMLStreamException { |
| try { |
| outputProcessorChain.reset(); |
| outputProcessorChain.processEvent(xmlSecEvent); |
| } catch (XMLSecurityException e) { |
| throw new XMLStreamException(e); |
| } catch (XMLStreamException e) { |
| String msg = e.getMessage(); |
| if (msg != null && msg.contains("Trying to declare prefix xmlns (illegal as per NS 1.1 #4)")) { |
| throw new XMLStreamException("If you hit this exception this most probably means" + |
| "you are using the javax.xml.transform.stax.StAXResult. Don't use " + |
| "it. It is buggy as hell.", e); |
| } |
| //NB1: net.java.dev.stax-utils also doesn work: [Fatal Error] |
| // :4:425: Attribute "xmlns" was already specified for element ... |
| //NB2: The spring version also doesn't work... |
| //it seems it is not trivial to write a StAXResult because I couldn't find an implementation |
| // which passes the testcases...hmm |
| throw e; |
| } |
| } |
| |
| private void outputOpenStartElement() throws XMLStreamException { |
| if (openStartElement != null) { |
| chainProcessEvent( |
| XMLSecEventFactory.createXmlSecStartElement( |
| openStartElement.getQName(), |
| openStartElement.getAttributes(), |
| openStartElement.getNamespaces())); |
| openStartElement = null; |
| } |
| if (haveToWriteEndElement) { |
| haveToWriteEndElement = false; |
| writeEndElement(); |
| } |
| } |
| |
| private String getNamespacePrefix(String namespaceURI) { |
| if (elementStack == null) { |
| return namespaceContext.getPrefix(namespaceURI); |
| } else { |
| return elementStack.getNamespaceContext().getPrefix(namespaceURI); |
| } |
| } |
| |
| @Override |
| public void writeStartElement(String localName) throws XMLStreamException { |
| writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, localName, XMLConstants.NULL_NS_URI); |
| } |
| |
| @Override |
| public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException { |
| writeStartElement(getNamespacePrefix(namespaceURI), localName, namespaceURI); |
| } |
| |
| @Override |
| public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { |
| outputOpenStartElement(); |
| |
| Element element; |
| if (elementStack == null) { |
| element = new Element(elementStack, namespaceContext, namespaceURI, localName, prefix); |
| if (signEntireRequestPart != null) { |
| signEntireRequestPart.setName(new QName(namespaceURI, localName, prefix)); |
| outputProcessorChain.getSecurityContext().putAsMap( |
| XMLSecurityConstants.SIGNATURE_PARTS, |
| signEntireRequestPart.getName(), |
| signEntireRequestPart |
| ); |
| } |
| if (encryptEntireRequestPart != null) { |
| encryptEntireRequestPart.setName(new QName(namespaceURI, localName, prefix)); |
| outputProcessorChain.getSecurityContext().putAsMap( |
| XMLSecurityConstants.ENCRYPTION_PARTS, |
| encryptEntireRequestPart.getName(), |
| encryptEntireRequestPart |
| ); |
| } |
| } else { |
| element = new Element(elementStack, namespaceURI, localName, prefix); |
| } |
| |
| elementStack = element; |
| openStartElement = element; |
| } |
| |
| @Override |
| public void writeEmptyElement(String localName) throws XMLStreamException { |
| writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, localName, XMLConstants.NULL_NS_URI); |
| } |
| |
| @Override |
| public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException { |
| writeEmptyElement(getNamespacePrefix(namespaceURI), localName, namespaceURI); |
| } |
| |
| @Override |
| public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException { |
| writeStartElement(prefix, localName, namespaceURI); |
| openStartElement.setEmptyElement(true); |
| haveToWriteEndElement = true; |
| } |
| |
| @Override |
| public void writeEndElement() throws XMLStreamException { |
| outputOpenStartElement(); |
| Element element = this.elementStack; |
| this.elementStack = this.elementStack.getParentElement(); |
| chainProcessEvent(XMLSecEventFactory.createXmlSecEndElement(element.getQName())); |
| |
| } |
| |
| @Override |
| public void writeEndDocument() throws XMLStreamException { |
| if (!endDocumentWritten) { |
| outputOpenStartElement(); |
| while (this.elementStack != null) { |
| Element element = this.elementStack; |
| this.elementStack = element.getParentElement(); |
| chainProcessEvent(XMLSecEventFactory.createXmlSecEndElement(element.getQName())); |
| } |
| chainProcessEvent(XMLSecEventFactory.createXMLSecEndDocument()); |
| endDocumentWritten = true; |
| } |
| } |
| |
| @Override |
| public void close() throws XMLStreamException { |
| try { |
| writeEndDocument(); |
| outputProcessorChain.reset(); |
| outputProcessorChain.doFinal(); |
| } catch (XMLSecurityException e) { |
| throw new XMLStreamException(e); |
| } |
| } |
| |
| @Override |
| public void flush() throws XMLStreamException { |
| } |
| |
| @Override |
| public void writeAttribute(String localName, String value) throws XMLStreamException { |
| writeAttribute(XMLConstants.DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI, localName, value); |
| } |
| |
| @Override |
| public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException { |
| writeAttribute(getNamespacePrefix(namespaceURI), namespaceURI, localName, value); |
| } |
| |
| @Override |
| public void writeAttribute(String prefix, String namespaceURI, String localName, String value) |
| throws XMLStreamException { |
| if (openStartElement == null) { |
| throw new XMLStreamException("No open start element."); |
| } |
| openStartElement.addAttribute( |
| XMLSecEventFactory.createXMLSecAttribute( |
| new QName(namespaceURI, localName, prefix), value)); |
| } |
| |
| @Override |
| public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException { |
| if (openStartElement == null) { |
| throw new XMLStreamException("No open start element."); |
| } |
| this.openStartElement.addNamespace(XMLSecEventFactory.createXMLSecNamespace(prefix, namespaceURI)); |
| } |
| |
| @Override |
| public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException { |
| if (openStartElement == null) { |
| throw new XMLStreamException("No open start element."); |
| } |
| //workaround for sun's stax parser |
| if (this.openStartElement.getElementPrefix().equals(XMLConstants.DEFAULT_NS_PREFIX)) { |
| this.openStartElement.setElementNamespace(namespaceURI); |
| this.openStartElement.setElementPrefix(XMLConstants.DEFAULT_NS_PREFIX); |
| } |
| this.openStartElement.addNamespace( |
| XMLSecEventFactory.createXMLSecNamespace(XMLConstants.DEFAULT_NS_PREFIX, namespaceURI)); |
| } |
| |
| @Override |
| public void writeComment(String data) throws XMLStreamException { |
| outputOpenStartElement(); |
| chainProcessEvent(XMLSecEventFactory.createXMLSecComment(data)); |
| } |
| |
| @Override |
| public void writeProcessingInstruction(String target) throws XMLStreamException { |
| writeProcessingInstruction(target, XMLConstants.DEFAULT_NS_PREFIX); |
| } |
| |
| @Override |
| public void writeProcessingInstruction(String target, String data) throws XMLStreamException { |
| outputOpenStartElement(); |
| chainProcessEvent(XMLSecEventFactory.createXMLSecProcessingInstruction(target, data)); |
| } |
| |
| @Override |
| public void writeCData(String data) throws XMLStreamException { |
| outputOpenStartElement(); |
| chainProcessEvent(XMLSecEventFactory.createXMLSecCData(data)); |
| } |
| |
| @Override |
| public void writeDTD(String dtd) throws XMLStreamException { |
| if (elementStack != null) { |
| throw new XMLStreamException("Not in proLOG"); |
| } |
| chainProcessEvent(XMLSecEventFactory.createXMLSecDTD(dtd)); |
| } |
| |
| @Override |
| public void writeEntityRef(final String name) throws XMLStreamException { |
| outputOpenStartElement(); |
| chainProcessEvent( |
| XMLSecEventFactory.createXMLSecEntityReference( |
| name, |
| XMLSecEventFactory.createXmlSecEntityDeclaration(name) |
| ) |
| ); |
| } |
| |
| @Override |
| public void writeStartDocument() throws XMLStreamException { |
| writeStartDocument(null, null); |
| } |
| |
| @Override |
| public void writeStartDocument(String version) throws XMLStreamException { |
| writeStartDocument(null, version); |
| } |
| |
| @Override |
| public void writeStartDocument(String encoding, String version) throws XMLStreamException { |
| chainProcessEvent(XMLSecEventFactory.createXmlSecStartDocument(null, encoding, null, version)); |
| } |
| |
| @Override |
| public void writeCharacters(String text) throws XMLStreamException { |
| outputOpenStartElement(); |
| chainProcessEvent(XMLSecEventFactory.createXmlSecCharacters(text)); |
| } |
| |
| @Override |
| public void writeCharacters(char[] text, int start, int len) throws XMLStreamException { |
| outputOpenStartElement(); |
| chainProcessEvent(XMLSecEventFactory.createXmlSecCharacters(text, start, len)); |
| } |
| |
| @Override |
| public String getPrefix(String uri) throws XMLStreamException { |
| return getNamespacePrefix(uri); |
| } |
| |
| @Override |
| public void setPrefix(String prefix, String uri) throws XMLStreamException { |
| if (elementStack == null) { |
| this.namespaceContext.add(prefix, uri); |
| } else { |
| this.elementStack.getNamespaceContext().add(prefix, uri); |
| } |
| } |
| |
| @Override |
| public void setDefaultNamespace(String uri) throws XMLStreamException { |
| if (elementStack == null) { |
| this.namespaceContext.add(XMLConstants.DEFAULT_NS_PREFIX, uri); |
| } else { |
| this.elementStack.getNamespaceContext().add(XMLConstants.DEFAULT_NS_PREFIX, uri); |
| } |
| } |
| |
| @Override |
| public void setNamespaceContext(NamespaceContext context) throws XMLStreamException { |
| if (context == null) { |
| throw new NullPointerException("context must not be null"); |
| } |
| this.namespaceContext = new NSContext(context); |
| } |
| |
| @Override |
| public NamespaceContext getNamespaceContext() { |
| if (this.elementStack == null) { |
| return namespaceContext; |
| } |
| return elementStack.getNamespaceContext(); |
| } |
| |
| @Override |
| public Object getProperty(String name) throws IllegalArgumentException { |
| throw new IllegalArgumentException("Properties not supported"); |
| } |
| |
| public SecurePart getSignEntireRequestPart() { |
| return signEntireRequestPart; |
| } |
| |
| public void setSignEntireRequestPart(SecurePart signEntireRequestPart) { |
| this.signEntireRequestPart = signEntireRequestPart; |
| } |
| |
| public SecurePart getEncryptEntireRequestPart() { |
| return encryptEntireRequestPart; |
| } |
| |
| public void setEncryptEntireRequestPart(SecurePart encryptEntireRequestPart) { |
| this.encryptEntireRequestPart = encryptEntireRequestPart; |
| } |
| |
| private static class Element { |
| |
| private Element parentElement; |
| |
| private QName qName; |
| private String elementName; |
| private String elementNamespace; |
| private String elementPrefix; |
| private boolean emptyElement; |
| private List<XMLSecNamespace> namespaces = Collections.emptyList(); |
| private List<XMLSecAttribute> attributes = Collections.emptyList(); |
| |
| private NSContext namespaceContext; |
| |
| public Element(Element parentElement, |
| String elementNamespace, String elementName, String elementPrefix) { |
| this(parentElement, null, elementNamespace, elementName, elementPrefix); |
| } |
| |
| public Element(Element parentElement, NSContext namespaceContext, |
| String elementNamespace, String elementName, String elementPrefix) { |
| this.parentElement = parentElement; |
| this.namespaceContext = namespaceContext; |
| this.elementName = elementName; |
| setElementNamespace(elementNamespace); |
| setElementPrefix(elementPrefix); |
| } |
| |
| private Element getParentElement() { |
| return parentElement; |
| } |
| |
| private void setEmptyElement(boolean emptyElement) { |
| this.emptyElement = emptyElement; |
| } |
| |
| private String getElementName() { |
| return elementName; |
| } |
| |
| private String getElementNamespace() { |
| return elementNamespace; |
| } |
| |
| private void setElementNamespace(String elementNamespace) { |
| if (elementNamespace == null) { |
| this.elementNamespace = XMLConstants.NULL_NS_URI; |
| } else { |
| this.elementNamespace = elementNamespace; |
| } |
| this.qName = null; |
| } |
| |
| private String getElementPrefix() { |
| return elementPrefix; |
| } |
| |
| private void setElementPrefix(String elementPrefix) { |
| if (elementPrefix == null) { |
| this.elementPrefix = XMLConstants.DEFAULT_NS_PREFIX; |
| } else { |
| this.elementPrefix = elementPrefix; |
| } |
| this.qName = null; |
| } |
| |
| private List<XMLSecNamespace> getNamespaces() { |
| return namespaces; |
| } |
| |
| private void addNamespace(XMLSecNamespace namespace) { |
| if (this.namespaces == Collections.<XMLSecNamespace>emptyList()) { |
| this.namespaces = new ArrayList<>(1); |
| } |
| this.namespaces.add(namespace); |
| |
| //also add namespace to namespace-context |
| getNamespaceContext().add(namespace.getPrefix(), namespace.getNamespaceURI()); |
| } |
| |
| private List<XMLSecAttribute> getAttributes() { |
| return attributes; |
| } |
| |
| private void addAttribute(XMLSecAttribute attribute) { |
| if (this.attributes == Collections.<XMLSecAttribute>emptyList()) { |
| this.attributes = new ArrayList<>(1); |
| } |
| this.attributes.add(attribute); |
| } |
| |
| private NSContext getNamespaceContext() { |
| if (this.namespaceContext == null) { |
| if (emptyElement) { |
| this.namespaceContext = parentElement.getNamespaceContext(); |
| } else if (parentElement != null) { |
| this.namespaceContext = new NSContext(parentElement.getNamespaceContext()); |
| } else { |
| this.namespaceContext = new NSContext(null); |
| } |
| } |
| return this.namespaceContext; |
| } |
| |
| private QName getQName() { |
| if (this.qName == null) { |
| this.qName = new QName(this.getElementNamespace(), this.getElementName(), this.getElementPrefix()); |
| } |
| return this.qName; |
| } |
| } |
| |
| private static class NSContext implements NamespaceContext { |
| |
| private NamespaceContext parentNamespaceContext; |
| private List<String> prefixNsList = Collections.emptyList(); |
| |
| NSContext(NamespaceContext parentNamespaceContext) { |
| this.parentNamespaceContext = parentNamespaceContext; |
| } |
| |
| @Override |
| public String getNamespaceURI(String prefix) { |
| for (int i = 0; i < prefixNsList.size(); i += 2) { |
| String s = prefixNsList.get(i); |
| if (s.equals(prefix)) { |
| return prefixNsList.get(i + 1); |
| } |
| } |
| |
| if (parentNamespaceContext != null) { |
| return parentNamespaceContext.getNamespaceURI(prefix); |
| } |
| return null; |
| } |
| |
| @Override |
| public String getPrefix(String namespaceURI) { |
| for (int i = 1; i < prefixNsList.size(); i += 2) { |
| String s = prefixNsList.get(i); |
| if (s.equals(namespaceURI)) { |
| return prefixNsList.get(i - 1); |
| } |
| } |
| |
| if (parentNamespaceContext != null) { |
| return parentNamespaceContext.getPrefix(namespaceURI); |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("rawtypes") |
| @Override |
| public Iterator getPrefixes(String namespaceURI) { |
| List<String> prefixes = new ArrayList<>(1); |
| for (int i = 1; i < prefixNsList.size(); i += 2) { |
| String s = prefixNsList.get(i); |
| if (s.equals(namespaceURI)) { |
| prefixes.add(prefixNsList.get(i - 1)); |
| } |
| } |
| |
| if (parentNamespaceContext != null) { |
| @SuppressWarnings("unchecked") |
| Iterator<String> parentPrefixes = parentNamespaceContext.getPrefixes(namespaceURI); |
| while (parentPrefixes.hasNext()) { |
| prefixes.add(parentPrefixes.next()); |
| } |
| } |
| return prefixes.iterator(); |
| } |
| |
| private void add(String prefix, String namespace) { |
| if (this.prefixNsList == Collections.<String>emptyList()) { |
| this.prefixNsList = new ArrayList<>(1); |
| } |
| this.prefixNsList.add(prefix); |
| this.prefixNsList.add(namespace); |
| } |
| } |
| } |