/* * 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.vysper.xmpp.modules.core.base.handler; import org.apache.vysper.xml.fragment.Attribute; import org.apache.vysper.xmpp.addressing.Entity; import org.apache.vysper.xmpp.addressing.EntityImpl; import org.apache.vysper.xmpp.protocol.NamespaceURIs; import org.apache.vysper.xmpp.protocol.ResponseStanzaContainer; import org.apache.vysper.xmpp.protocol.ResponseStanzaContainerImpl; import org.apache.vysper.xmpp.protocol.SessionStateHolder; import org.apache.vysper.xmpp.protocol.StanzaHandler; import org.apache.vysper.xmpp.protocol.StreamErrorCondition; import org.apache.vysper.xmpp.server.ServerRuntimeContext; import org.apache.vysper.xmpp.server.SessionContext; import org.apache.vysper.xmpp.server.response.ServerErrorResponses; import org.apache.vysper.xmpp.stanza.IQStanzaType; import org.apache.vysper.xmpp.stanza.Stanza; import org.apache.vysper.xmpp.stanza.XMPPCoreStanza; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * foundation for the three core protocol stanzas: iq, message, presence * * @author The Apache MINA Project (dev@mina.apache.org) */ public abstract class XMPPCoreStanzaHandler implements StanzaHandler { final static Logger logger = LoggerFactory.getLogger(XMPPCoreStanzaHandler.class); public boolean verify(Stanza stanza) { if(stanza == null) return false; boolean typeVerified = verifyType(stanza); boolean namespaceVerified = verifyNamespace(stanza); return typeVerified && namespaceVerified; } public boolean isSessionRequired() { return true; } protected abstract boolean verifyType(Stanza stanza); protected boolean verifyNamespace(Stanza stanza) { return NamespaceURIs.JABBER_CLIENT.equals(stanza.getNamespaceURI()); } public ResponseStanzaContainer execute(Stanza anyStanza, ServerRuntimeContext serverRuntimeContext, boolean isOutboundStanza, SessionContext sessionContext, SessionStateHolder sessionStateHolder) { XMPPCoreStanza stanza = XMPPCoreStanza.getWrapper(anyStanza); if (stanza == null) throw new IllegalArgumentException("can only handle core XMPP stanzas (iq, message, presence)"); // type="error" is common to all stanza, check here some prerequisites Attribute typeAttribute = stanza.getAttribute("type"); XMPPCoreStanza xmppCoreStanza = XMPPCoreStanza.getWrapper(stanza); if (xmppCoreStanza != null && typeAttribute != null) { String errorDescription = null; String type = typeAttribute.getValue(); if (IQStanzaType.ERROR.value().equals(type)) { // assure, result contains zero or one element // rfc3920/9.2.3/7. if (!stanza.getVerifier().subElementPresent("error")) { errorDescription = "stanza of type error must include an 'error' child"; } } else { // assure, non-error result does not contain error // rfc3920/9.2.3/7. + rfc3920/9.3.1/3. if (stanza.getVerifier().subElementPresent("error")) { errorDescription = "stanza which is not of type error must not include an 'error' child"; } } // at this point, we are not allowed to respond with another error // we cannot really close the stream // we simply ignore it. /*ResponseStanzaContainerImpl errorResponseContainer = new ResponseStanzaContainerImpl( ServerErrorResponses.getInstance().getErrorResponse(xmppCoreStanza, StanzaErrorType.MODIFY, StanzaErrorCondition.BAD_REQUEST, errorDescription, sessionContext.getXMLLang(), null) ); return errorResponseContainer;*/ } Entity to = stanza.getTo(); if (sessionContext != null && sessionContext.isServerToServer() && to == null) { // "to" MUST be present for jabber:server return new ResponseStanzaContainerImpl(ServerErrorResponses.getStreamError( StreamErrorCondition.IMPROPER_ADDRESSING, stanza.getXMLLang(), "missing to attribute", null)); } if (to != null) { // TODO ensure, that RFC3920 9.1.1 "If the value of the 'to' attribute is invalid or cannot be contacted..." is enforced } Stanza responseStanza = executeCore(stanza, serverRuntimeContext, isOutboundStanza, sessionContext); if (responseStanza != null) return new ResponseStanzaContainerImpl(responseStanza); return null; } protected abstract Stanza executeCore(XMPPCoreStanza stanza, ServerRuntimeContext serverRuntimeContext, boolean isOutboundStanza, SessionContext sessionContext); /** * Extracts the from address either from the "from" attribute of the stanza, if this isn't given * retracts to using the address of the initiating entity plus the resource of the sessionContext (if available). * * A client might send a stanza without a 'from' attribute, if the sending (bare or full) entity can be determined * from the context. such a missing from is determined here, if possible. * for a formal discussion, see RFC3921bis/Resource Binding/Binding multiple resources/From Addresses * * @param stanza * @param sessionContext * @return The JID of the sender, either from the stanza or the context. A bare JID is returned if no, or more than one resource is bound. */ public static Entity extractSenderJID(XMPPCoreStanza stanza, SessionContext sessionContext) { Entity from = stanza.getFrom(); if (from == null) { from = new EntityImpl(sessionContext.getInitiatingEntity(), sessionContext.getServerRuntimeContext() .getResourceRegistry().getUniqueResourceForSession(sessionContext)); } return from; } /** * Extracts the from address either from the "from" attribute of the stanza, if this isn't given * retracts to using the address of the initiating entity plus the resource of the sessionContext. * * A client might send a stanza without a 'from' attribute, if the sending (bare or full) entity can be determined * from the context. such a missing from is determined here, if possible. * for a formal discussion, see RFC3921bis/Resource Binding/Binding multiple resources/From Addresses * * @param stanza * @param sessionContext * @return The JID of the sender, either from the stanza or the context. If there is no, or multiple resources bound, it returns null. */ public static Entity extractUniqueSenderJID(XMPPCoreStanza stanza, SessionContext sessionContext) { Entity from = stanza.getFrom(); if (from != null) { return from; } // Use the information stored within the context Entity initiatingEntity = sessionContext.getInitiatingEntity(); if (initiatingEntity == null) { throw new RuntimeException("no 'from' attribute, and initiating entity not set"); } String resourceId = sessionContext.getServerRuntimeContext().getResourceRegistry().getUniqueResourceForSession( sessionContext); if (resourceId == null) { logger .warn( "no 'from' attribute, and cannot uniquely determine sending resource for initiating entity {} in session {}", initiatingEntity.getFullQualifiedName(), sessionContext.getSessionId()); return null; } return new EntityImpl(initiatingEntity, resourceId); } }