/** * 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.cxf.binding.soap.interceptor; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.xml.namespace.QName; import javax.xml.stream.XMLEventFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.events.XMLEvent; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.apache.cxf.Bus; import org.apache.cxf.annotations.SchemaValidation.SchemaValidationType; import org.apache.cxf.binding.soap.Soap11; import org.apache.cxf.binding.soap.Soap12; import org.apache.cxf.binding.soap.SoapFault; import org.apache.cxf.binding.soap.SoapHeader; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.binding.soap.SoapVersion; import org.apache.cxf.binding.soap.SoapVersionFactory; import org.apache.cxf.common.i18n.Message; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.databinding.DataBinding; import org.apache.cxf.databinding.DataReader; import org.apache.cxf.headers.HeaderManager; import org.apache.cxf.headers.HeaderProcessor; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.helpers.ServiceUtils; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.message.MessageUtils; import org.apache.cxf.phase.Phase; import org.apache.cxf.staxutils.PartialXMLStreamReader; import org.apache.cxf.staxutils.StaxUtils; import org.apache.cxf.staxutils.StaxUtils.StreamToDOMContext; import org.apache.cxf.staxutils.W3CDOMStreamWriter; public class ReadHeadersInterceptor extends AbstractSoapInterceptor { public static final String ENVELOPE_EVENTS = "envelope.events"; public static final String BODY_EVENTS = "body.events"; public static final String ENVELOPE_PREFIX = "envelope.prefix"; public static final String BODY_PREFIX = "body.prefix"; /** * */ public static class CheckClosingTagsInterceptor extends AbstractSoapInterceptor { public CheckClosingTagsInterceptor() { super(Phase.POST_LOGICAL); } /** {@inheritDoc}*/ public void handleMessage(SoapMessage message) throws Fault { XMLStreamReader xmlReader = message.getContent(XMLStreamReader.class); if (xmlReader != null) { try { while (xmlReader.hasNext()) { if (xmlReader.next() == XMLStreamReader.END_DOCUMENT) { return; } } } catch (XMLStreamException e) { throw new SoapFault(e.getMessage(), e, message.getVersion().getSender()); } } } } private static final Logger LOG = LogUtils.getL7dLogger(ReadHeadersInterceptor.class); private Bus bus; private SoapVersion version; public ReadHeadersInterceptor(Bus b) { super(Phase.READ); bus = b; } public ReadHeadersInterceptor(Bus b, SoapVersion v) { super(Phase.READ); version = v; bus = b; } public ReadHeadersInterceptor(Bus b, String phase) { super(phase); bus = b; } public static SoapVersion readVersion(XMLStreamReader xmlReader, SoapMessage message) { String ns = xmlReader.getNamespaceURI(); String lcname = xmlReader.getLocalName(); if (ns == null || "".equals(ns)) { throw new SoapFault(new Message("NO_NAMESPACE", LOG, lcname), Soap11.getInstance().getVersionMismatch()); } SoapVersion soapVersion = SoapVersionFactory.getInstance().getSoapVersion(ns); if (soapVersion == null) { throw new SoapFault(new Message("INVALID_VERSION", LOG, ns, lcname), Soap11.getInstance().getVersionMismatch()); } if (!"Envelope".equals(lcname)) { throw new SoapFault(new Message("INVALID_ENVELOPE", LOG, lcname), soapVersion.getSender()); } message.setVersion(soapVersion); return soapVersion; } //CHECKSTYLE:OFF MethodLength public void handleMessage(SoapMessage message) { if (isGET(message)) { LOG.fine("ReadHeadersInterceptor skipped in HTTP GET method"); return; } /* * Reject OPTIONS, and any other noise that is not allowed in SOAP. */ final String verb = (String) message.get(org.apache.cxf.message.Message.HTTP_REQUEST_METHOD); if (verb != null && !"POST".equals(verb)) { Fault formula405 = new Fault("HTTP verb was not GET or POST", LOG); formula405.setStatusCode(405); throw formula405; } XMLStreamReader xmlReader = message.getContent(XMLStreamReader.class); boolean closeNeeded = false; if (xmlReader == null) { InputStream in = message.getContent(InputStream.class); if (in == null) { throw new RuntimeException("Can't find input stream in message"); } xmlReader = StaxUtils.createXMLStreamReader(in); closeNeeded = true; } try { if (xmlReader.getEventType() == XMLStreamConstants.START_ELEMENT || xmlReader.nextTag() == XMLStreamConstants.START_ELEMENT) { SoapVersion soapVersion = readVersion(xmlReader, message); if (soapVersion == Soap12.getInstance() && version == Soap11.getInstance()) { message.setVersion(version); throw new SoapFault(new Message("INVALID_11_VERSION", LOG), version.getVersionMismatch()); } XMLStreamReader filteredReader = new PartialXMLStreamReader(xmlReader, message.getVersion() .getBody()); Node nd = message.getContent(Node.class); W3CDOMStreamWriter writer = message.get(W3CDOMStreamWriter.class); Document doc = null; if (writer != null) { StaxUtils.copy(filteredReader, writer); doc = writer.getDocument(); } else if (nd instanceof Document) { doc = (Document)nd; StaxUtils.readDocElements(doc, doc, filteredReader, false, false); } else { final boolean addNC = MessageUtils.getContextualBoolean( message, "org.apache.cxf.binding.soap.addNamespaceContext", false); Map<String, String> bodyNC = addNC ? new HashMap<String, String>() : null; if (addNC) { // add the Envelope-Level declarations addCurrentNamespaceDecls(xmlReader, bodyNC); } HeadersProcessor processor = new HeadersProcessor(soapVersion); doc = processor.process(filteredReader); if (doc != null) { message.setContent(Node.class, doc); } else { message.put(ENVELOPE_EVENTS, processor.getEnvAttributeAndNamespaceEvents()); message.put(BODY_EVENTS, processor.getBodyAttributeAndNamespaceEvents()); message.put(ENVELOPE_PREFIX, processor.getEnvelopePrefix()); message.put(BODY_PREFIX, processor.getBodyPrefix()); } if (addNC) { // add the Body-level declarations addCurrentNamespaceDecls(xmlReader, bodyNC); message.put("soap.body.ns.context", bodyNC); } } // Find header if (doc != null) { Element element = doc.getDocumentElement(); QName header = soapVersion.getHeader(); List<Element> elemList = DOMUtils.findAllElementsByTagNameNS(element, header.getNamespaceURI(), header.getLocalPart()); for (Element elem : elemList) { Element hel = DOMUtils.getFirstElement(elem); while (hel != null) { // Need to add any attributes that are present on the parent element // which otherwise would be lost. if (elem.hasAttributes()) { NamedNodeMap nnp = elem.getAttributes(); for (int ct = 0; ct < nnp.getLength(); ct++) { Node attr = nnp.item(ct); Node headerAttrNode = hel.hasAttributes() ? hel.getAttributes() .getNamedItemNS(attr.getNamespaceURI(), attr.getLocalName()) : null; if (headerAttrNode == null) { Attr attribute = hel.getOwnerDocument() .createAttributeNS(attr.getNamespaceURI(), attr.getNodeName()); attribute.setNodeValue(attr.getNodeValue()); hel.setAttributeNodeNS(attribute); } } } HeaderProcessor p = bus == null ? null : bus.getExtension(HeaderManager.class) .getHeaderProcessor(hel.getNamespaceURI()); Object obj; DataBinding dataBinding = null; if (p == null || p.getDataBinding() == null) { obj = hel; } else { dataBinding = p.getDataBinding(); DataReader<Node> dataReader = dataBinding.createReader(Node.class); dataReader.setAttachments(message.getAttachments()); dataReader.setProperty(DataReader.ENDPOINT, message.getExchange().getEndpoint()); dataReader.setProperty(Message.class.getName(), message); obj = dataReader.read(hel); } // TODO - add the interceptors SoapHeader shead = new SoapHeader(new QName(hel.getNamespaceURI(), hel.getLocalName()), obj, dataBinding); String mu = hel.getAttributeNS(soapVersion.getNamespace(), soapVersion.getAttrNameMustUnderstand()); String act = hel.getAttributeNS(soapVersion.getNamespace(), soapVersion.getAttrNameRole()); if (!StringUtils.isEmpty(act)) { shead.setActor(act); } shead.setMustUnderstand(Boolean.valueOf(mu) || "1".equals(mu)); // mark header as inbound header.(for distinguishing between the direction to // avoid piggybacking of headers from request->server->response. shead.setDirection(SoapHeader.Direction.DIRECTION_IN); message.getHeaders().add(shead); hel = DOMUtils.getNextElement(hel); } } } if (ServiceUtils.isSchemaValidationEnabled(SchemaValidationType.IN, message)) { message.getInterceptorChain().add(new CheckClosingTagsInterceptor()); } } } catch (XMLStreamException e) { throw new SoapFault(new Message("XML_STREAM_EXC", LOG, e.getMessage()), e, message.getVersion().getSender()); } finally { if (closeNeeded) { try { StaxUtils.close(xmlReader); } catch (XMLStreamException e) { throw new SoapFault(new Message("XML_STREAM_EXC", LOG, e.getMessage()), e, message.getVersion().getSender()); } } } } //CHECKSTYLE:ON private void addCurrentNamespaceDecls(XMLStreamReader xmlReader, Map<String, String> bodyNsMap) { for (int i = 0; i < xmlReader.getNamespaceCount(); i++) { String nsuri = xmlReader.getNamespaceURI(i); if ("".equals(nsuri)) { bodyNsMap.remove(""); } else { bodyNsMap.put(xmlReader.getNamespacePrefix(i), nsuri); } } } /** * A convenient class for parsing the message header stream into a DOM document; * the document is created only if a SOAP Header is actually found, keeping the * memory usage as low as possible (there's no reason for building the DOM doc * here if there's actually no header in the message, but we need to figure that * out while parsing the stream). */ private static class HeadersProcessor { private static XMLEventFactory eventFactory; private final String ns; private final String header; private final String body; private final String envelope; private final List<XMLEvent> events = new ArrayList<>(8); private List<XMLEvent> envEvents; private List<XMLEvent> bodyEvents; private StreamToDOMContext context; private Document doc; private Node parent; private QName lastStartElementQName; private String envelopePrefix; private String bodyPrefix; static { try { eventFactory = XMLEventFactory.newInstance(); } catch (Throwable t) { //explicity create woodstox event factory as last try eventFactory = StaxUtils.createWoodstoxEventFactory(); } } HeadersProcessor(SoapVersion version) { this.header = version.getHeader().getLocalPart(); this.ns = version.getEnvelope().getNamespaceURI(); this.envelope = version.getEnvelope().getLocalPart(); this.body = version.getBody().getLocalPart(); } public Document process(XMLStreamReader reader) throws XMLStreamException { // number of elements read in int read = 0; int event = reader.getEventType(); while (reader.hasNext()) { switch (event) { case XMLStreamConstants.START_ELEMENT: read++; addEvent(eventFactory.createStartElement(new QName(reader.getNamespaceURI(), reader .getLocalName(), reader.getPrefix()), null, null)); for (int i = 0; i < reader.getNamespaceCount(); i++) { addEvent(eventFactory.createNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i))); } for (int i = 0; i < reader.getAttributeCount(); i++) { addEvent(eventFactory.createAttribute(reader.getAttributePrefix(i), reader.getAttributeNamespace(i), reader.getAttributeLocalName(i), reader.getAttributeValue(i))); } if (doc != null) { //go on parsing the stream directly till the end and stop generating events StaxUtils.readDocElements(doc, parent, reader, context); } break; case XMLStreamConstants.END_ELEMENT: if (read > 0) { addEvent(eventFactory.createEndElement(new QName(reader.getNamespaceURI(), reader .getLocalName(), reader.getPrefix()), null)); } read--; break; case XMLStreamConstants.CHARACTERS: String s = reader.getText(); if (s != null) { addEvent(eventFactory.createCharacters(s)); } break; case XMLStreamConstants.COMMENT: addEvent(eventFactory.createComment(reader.getText())); break; case XMLStreamConstants.CDATA: addEvent(eventFactory.createCData(reader.getText())); break; case XMLStreamConstants.START_DOCUMENT: case XMLStreamConstants.END_DOCUMENT: case XMLStreamConstants.ATTRIBUTE: case XMLStreamConstants.NAMESPACE: break; default: break; } event = reader.next(); } return doc; } private void addEvent(XMLEvent event) { if (event.isStartElement()) { lastStartElementQName = event.asStartElement().getName(); if (header.equals(lastStartElementQName.getLocalPart()) && ns.equals(lastStartElementQName.getNamespaceURI())) { // process all events recorded so far context = new StreamToDOMContext(true, false, false); doc = DOMUtils.createDocument(); parent = doc; try { for (XMLEvent ev : events) { parent = StaxUtils.readDocElement(doc, parent, ev, context); } } catch (XMLStreamException e) { throw new Fault(e); } } else { if (ns.equals(lastStartElementQName.getNamespaceURI())) { if (body.equals(lastStartElementQName.getLocalPart())) { bodyPrefix = lastStartElementQName.getPrefix(); } else if (envelope.equals(lastStartElementQName.getLocalPart())) { envelopePrefix = lastStartElementQName.getPrefix(); } } events.add(event); } } else { if (event.isNamespace() || event.isAttribute()) { final String lastEl = lastStartElementQName.getLocalPart(); if (body.equals(lastEl) && ns.equals(lastStartElementQName.getNamespaceURI())) { if (bodyEvents == null) { bodyEvents = new ArrayList<>(); } bodyEvents.add(event); } else if (envelope.equals(lastEl) && ns.equals(lastStartElementQName.getNamespaceURI())) { if (envEvents == null) { envEvents = new ArrayList<>(); } envEvents.add(event); } } events.add(event); } } public List<XMLEvent> getBodyAttributeAndNamespaceEvents() { if (bodyEvents == null) { return Collections.emptyList(); } else { return Collections.unmodifiableList(bodyEvents); } } public List<XMLEvent> getEnvAttributeAndNamespaceEvents() { if (envEvents == null) { return Collections.emptyList(); } else { return Collections.unmodifiableList(envEvents); } } public String getEnvelopePrefix() { return envelopePrefix; } public String getBodyPrefix() { return bodyPrefix; } } }