blob: d26630f38f187ec8ad34494570db818f3d32bf1c [file] [log] [blame]
/*
* 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()) ||
NamespaceURIs.JABBER_SERVER.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);
}
}