Commit bac0519e authored by Dave Cridland's avatar Dave Cridland Committed by GitHub

Merge pull request #670 from surevine/mam-update

OF-1214 Update MAM (XEP-0313) to support :0 and :1 versions
parents f363143c 6335c1c0
......@@ -2,21 +2,42 @@ package org.jivesoftware.openfire.forward;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketExtension;
import java.util.Date;
/**
* @author Christian Schudt
*/
public class Forwarded extends PacketExtension {
public Forwarded(Element copy, Date delay, JID delayFrom) {
super("forwarded", "urn:xmpp:forward:0");
populate(copy, delay, delayFrom);
}
public Forwarded(Message message, Date delay, JID delayFrom) {
super("forwarded", "urn:xmpp:forward:0");
Message copy = message.createCopy();
populate(copy.getElement(), delay, delayFrom);
}
public Forwarded(Element copy) {
super("forwarded", "urn:xmpp:forward:0");
populate(copy, null, null);
}
public Forwarded(Message message) {
super("forwarded", "urn:xmpp:forward:0");
Message copy = message.createCopy();
populate(copy.getElement(), null, null);
}
private void populate(Element copy, Date delay, JID delayFrom) {
copy.getElement().setQName(QName.get("message", "jabber:client"));
copy.setQName(QName.get("message", "jabber:client"));
for (Object element : copy.getElement().elements()) {
for (Object element : copy.elements()) {
if (element instanceof Element) {
Element el = (Element) element;
// Only set the "jabber:client" namespace if the namespace is empty (otherwise the resulting xml would look like <body xmlns=""/>)
......@@ -25,6 +46,14 @@ public class Forwarded extends PacketExtension {
}
}
}
element.add(copy.getElement());
if (delay != null) {
Element delayInfo = element.addElement("delay", "urn:xmpp:delay");
delayInfo.addAttribute("stamp", XMPPDateTimeFormat.format(delay));
if (delayFrom != null) {
// Set the Full JID as the "from" attribute
delayInfo.addAttribute("from", delayFrom.toString());
}
}
element.add(copy);
}
}
......@@ -129,11 +129,13 @@ public class JdbcPersistenceManager implements PersistenceManager {
+ "ofMessageArchive.toJID, " + "ofMessageArchive.sentDate, " + "ofMessageArchive.stanza, "
+ "ofMessageArchive.messageID, " + "ofConParticipant.bareJID "
+ "FROM ofMessageArchive "
+ "INNER JOIN ofConParticipant ON ofMessageArchive.conversationID = ofConParticipant.conversationID ";
+ "INNER JOIN ofConParticipant ON ofMessageArchive.conversationID = ofConParticipant.conversationID "
+ "WHERE ofMessageArchive.stanza != NULL OR ofMessageArchive.body != NULL";
public static final String COUNT_MESSAGES = "SELECT COUNT(DISTINCT ofMessageArchive.messageID) "
+ "FROM ofMessageArchive "
+ "INNER JOIN ofConParticipant ON ofMessageArchive.conversationID = ofConParticipant.conversationID ";
+ "INNER JOIN ofConParticipant ON ofMessageArchive.conversationID = ofConParticipant.conversationID "
+ "WHERE ofMessageArchive.stanza != NULL OR ofMessageArchive.body != NULL";
public boolean createMessage(ArchivedMessage message) {
/* read only */
......
package com.reucon.openfire.plugin.archive.xep0313;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
import org.dom4j.*;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.jivesoftware.openfire.forward.Forwarded;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.forms.DataForm;
import org.xmpp.forms.FormField;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.*;
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
import com.reucon.openfire.plugin.archive.xep.AbstractIQHandler;
......@@ -31,22 +27,22 @@ import com.reucon.openfire.plugin.archive.xep0059.XmppResultSet;
/**
* XEP-0313 IQ Query Handler
*/
public class IQQueryHandler extends AbstractIQHandler implements
abstract class IQQueryHandler extends AbstractIQHandler implements
ServerFeaturesProvider {
private static final Logger Log = LoggerFactory.getLogger(IQHandler.class);
private static final String NAMESPACE = "urn:xmpp:mam:0";
private static final String MODULE_NAME = "Message Archive Management Query Handler";
protected final String NAMESPACE;
XMPPDateTimeFormat xmppDateTimeFormat = new XMPPDateTimeFormat();
private final XMPPDateTimeFormat xmppDateTimeFormat = new XMPPDateTimeFormat();
protected IQQueryHandler() {
super(MODULE_NAME, "query", NAMESPACE);
IQQueryHandler(final String moduleName, final String namespace) {
super(moduleName, "query", namespace);
NAMESPACE = namespace;
}
public IQ handleIQ(IQ packet) throws UnauthorizedException {
LocalClientSession session = (LocalClientSession) sessionManager.getSession(packet.getFrom());
Session session = sessionManager.getSession(packet.getFrom());
// If no session was found then answer with an error (if possible)
if (session == null) {
......@@ -58,21 +54,36 @@ public class IQQueryHandler extends AbstractIQHandler implements
}
if(packet.getType().equals(IQ.Type.get)) {
sendSupportedFieldsResult(packet, session);
return null;
return buildSupportedFieldsResult(packet, session);
}
// Default to user's own archive
JID archiveJid = packet.getFrom();
JID archiveJid = packet.getTo();
if (archiveJid == null) {
archiveJid = packet.getFrom().asBareJID();
}
Log.debug("Archive requested is {}", archiveJid);
// Now decide the type.
boolean muc = false;
if (!XMPPServer.getInstance().isLocal(archiveJid)) {
Log.debug("Archive is not local (user)");
return buildErrorResponse(packet);
}
if(packet.getElement().attribute("to") != null) {
archiveJid = new JID(packet.getElement().attribute("to").getStringValue());
// Only allow queries to users own archives
if(!archiveJid.toBareJID().equals(packet.getFrom().toBareJID())) {
JID requestor = packet.getFrom().asBareJID();
Log.debug("Requestor is {} for muc=={}", requestor, muc);
// Auth checking.
if(!archiveJid.equals(requestor)) { // Not user's own
// ... disallow unless admin.
if (!XMPPServer.getInstance().getAdmins().contains(requestor)) {
return buildForbiddenResponse(packet);
}
}
sendMidQuery(packet, session);
final QueryRequest queryRequest = new QueryRequest(packet.getChildElement(), archiveJid);
Collection<ArchivedMessage> archivedMessages = retrieveMessages(queryRequest);
......@@ -80,17 +91,21 @@ public class IQQueryHandler extends AbstractIQHandler implements
sendMessageResult(session, queryRequest, archivedMessage);
}
sendFinalMessage(session, queryRequest);
sendAcknowledgementResult(packet, session);
sendEndQuery(packet, session, queryRequest);
return null;
}
protected void sendMidQuery(IQ packet, Session session) {
// Default: Do nothing.
}
protected abstract void sendEndQuery(IQ packet, Session session, QueryRequest queryRequest);
/**
* Create error response to send to client
* @param packet
* @return
* @param packet IQ stanza received
* @return IQ stanza to be sent.
*/
private IQ buildErrorResponse(IQ packet) {
IQ reply = IQ.createResultIQ(packet);
......@@ -160,7 +175,7 @@ public class IQQueryHandler extends AbstractIQHandler implements
* @param packet Received query packet
* @param session Client session to respond to
*/
private void sendAcknowledgementResult(IQ packet, LocalClientSession session) {
private void sendAcknowledgementResult(IQ packet, Session session) {
IQ result = IQ.createResultIQ(packet);
session.process(result);
}
......@@ -170,7 +185,7 @@ public class IQQueryHandler extends AbstractIQHandler implements
* @param session Client session to respond to
* @param queryRequest Received query request
*/
private void sendFinalMessage(LocalClientSession session,
private void sendFinalMessage(Session session,
final QueryRequest queryRequest) {
Message finalMessage = new Message();
......@@ -199,44 +214,37 @@ public class IQQueryHandler extends AbstractIQHandler implements
* @param archivedMessage Message to send to client
* @return
*/
private void sendMessageResult(LocalClientSession session,
private void sendMessageResult(Session session,
QueryRequest queryRequest, ArchivedMessage archivedMessage) {
if(archivedMessage.getStanza() == null) {
// Don't send legacy archived messages (that have no stanza)
return;
String stanzaText = archivedMessage.getStanza();
if(stanzaText == null || stanzaText.equals("")) {
// Try creating a fake one from the body.
if (archivedMessage.getBody() != null && !archivedMessage.getBody().equals("")) {
stanzaText = String.format("<message from=\"{}\" to=\"{}\" type=\"chat\"><body>{}</body>", archivedMessage.getWithJid(), archivedMessage.getWithJid(), archivedMessage.getBody());
} else {
// Don't send legacy archived messages (that have no stanza)
return;
}
}
Message messagePacket = new Message();
messagePacket.setTo(session.getAddress());
Element result = messagePacket.addChildElement("result", NAMESPACE);
result.addAttribute("id", archivedMessage.getId().toString());
if(queryRequest.getQueryid() != null) {
result.addAttribute("queryid", queryRequest.getQueryid());
}
Element forwarded = result.addElement("forwarded", "urn:xmpp:forward:0");
Element delay = forwarded.addElement("delay", "urn:xmpp:delay");
delay.addAttribute("stamp", XMPPDateTimeFormat.format(archivedMessage.getTime()));
Forwarded fwd;
Document stanza;
try {
stanza = DocumentHelper.parseText(archivedMessage.getStanza());
if ( stanza.getRootElement().getNamespaceURI() == null || stanza.getRootElement().getNamespaceURI().isEmpty() )
{
// OF-1132: If no 'xmlns' is set for the stanza then as per XML namespacing rules it would inherit the
// 'urn:xmpp:forward:0' namespace, which is wrong (see XEP-0297).
stanza.getRootElement().setQName( QName.get( stanza.getRootElement().getName(), "jabber:client") );
}
forwarded.add(stanza.getRootElement());
stanza = DocumentHelper.parseText(stanzaText);
fwd = new Forwarded(stanza.getRootElement(), archivedMessage.getTime(), null);
} catch (DocumentException e) {
Log.error("Failed to parse message stanza.", e);
// If we can't parse stanza then we have no message to send to client, abort
return;
}
if (fwd == null) return; // Shouldn't be possible.
messagePacket.addExtension(new Result(fwd, NAMESPACE, queryRequest.getQueryid(), archivedMessage.getId().toString()));
session.process(messagePacket);
}
......@@ -245,7 +253,7 @@ public class IQQueryHandler extends AbstractIQHandler implements
* @param packet Incoming query (form field request) packet
* @param session Session with client
*/
private void sendSupportedFieldsResult(IQ packet, LocalClientSession session) {
private IQ buildSupportedFieldsResult(IQ packet, Session session) {
IQ result = IQ.createResultIQ(packet);
......@@ -260,7 +268,7 @@ public class IQQueryHandler extends AbstractIQHandler implements
query.add(form.getElement());
session.process(result);
return result;
}
@Override
......@@ -268,4 +276,18 @@ public class IQQueryHandler extends AbstractIQHandler implements
return Collections.singleton(NAMESPACE).iterator();
}
void completeFinElement(QueryRequest queryRequest, Element fin) {
if(queryRequest.getQueryid() != null) {
fin.addAttribute("queryid", queryRequest.getQueryid());
}
XmppResultSet resultSet = queryRequest.getResultSet();
if (resultSet != null) {
fin.add(resultSet.createResultElement());
if(resultSet.isComplete()) {
fin.addAttribute("complete", "true");
}
}
}
}
package com.reucon.openfire.plugin.archive.xep0313;
import org.dom4j.*;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
/**
* XEP-0313 IQ Query Handler
*/
class IQQueryHandler0 extends IQQueryHandler {
private static final Logger Log = LoggerFactory.getLogger(IQHandler.class);
private static final String MODULE_NAME = "Message Archive Management Query Handler v0";
IQQueryHandler0() {
super(MODULE_NAME, "urn:xmpp:mam:0");
}
@Override
protected void sendMidQuery(IQ packet, Session session) {
sendAcknowledgementResult(packet, session);
}
@Override
protected void sendEndQuery(IQ packet, Session session, QueryRequest queryRequest) {
sendFinalMessage(session, queryRequest);
}
/**
* Send result packet to client acknowledging query.
* @param packet Received query packet
* @param session Client session to respond to
*/
private void sendAcknowledgementResult(IQ packet, Session session) {
IQ result = IQ.createResultIQ(packet);
session.process(result);
}
/**
* Send final message back to client following query.
* @param session Client session to respond to
* @param queryRequest Received query request
*/
private void sendFinalMessage(Session session,
final QueryRequest queryRequest) {
Message finalMessage = new Message();
finalMessage.setTo(session.getAddress());
Element fin = finalMessage.addChildElement("fin", NAMESPACE);
completeFinElement(queryRequest, fin);
session.process(finalMessage);
}
}
package com.reucon.openfire.plugin.archive.xep0313;
import org.dom4j.*;
import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
/**
* XEP-0313 IQ Query Handler
*/
class IQQueryHandler1 extends IQQueryHandler {
private static final Logger Log = LoggerFactory.getLogger(IQQueryHandler1.class);
private static final String MODULE_NAME = "Message Archive Management Query Handler v1";
IQQueryHandler1() {
super(MODULE_NAME, "urn:xmpp:mam:1");
}
@Override
protected void sendEndQuery(IQ packet, Session session, QueryRequest queryRequest) {
sendAcknowledgementResult(packet, session, queryRequest);
}
/**
* Send result packet to client acknowledging query.
* @param packet Received query packet
* @param session Client session to respond to
*/
private void sendAcknowledgementResult(IQ packet, Session session, QueryRequest queryRequest) {
IQ result = IQ.createResultIQ(packet);
Element fin = result.setChildElement("fin", NAMESPACE);
completeFinElement(queryRequest, fin);
session.process(result);
}
}
package com.reucon.openfire.plugin.archive.xep0313;
import org.jivesoftware.openfire.forward.Forwarded;
import org.xmpp.packet.PacketExtension;
import java.util.Date;
/**
* Created by dwd on 26/07/16.
*/
public final class Result extends PacketExtension {
public Result(Forwarded forwarded, String xmlns, String queryId, String id) {
super("result", xmlns);
element.add(forwarded.getElement());
}
}
......@@ -18,8 +18,8 @@ public class Xep0313Support extends AbstractXepSupport {
public Xep0313Support(XMPPServer server) {
super(server, NAMESPACE,NAMESPACE, "XEP-0313 IQ Dispatcher");
this.iqHandlers = new ArrayList<IQHandler>();
iqHandlers.add(new IQQueryHandler());
this.iqHandlers = new ArrayList<>();
iqHandlers.add(new IQQueryHandler0());
}
}
package com.reucon.openfire.plugin.archive.xep0313;
import com.reucon.openfire.plugin.archive.xep.AbstractXepSupport;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.handler.IQHandler;
import java.util.ArrayList;
/**
* Encapsulates support for <a
* href="http://www.xmpp.org/extensions/xep-0313.html">XEP-0313</a>.
*/
public class Xep0313Support1 extends AbstractXepSupport {
private static final String NAMESPACE = "urn:xmpp:mam:1";
public Xep0313Support1(XMPPServer server) {
super(server, NAMESPACE,NAMESPACE, "XEP-0313 IQ Dispatcher");
this.iqHandlers = new ArrayList<>();
iqHandlers.add(new IQQueryHandler1());
}
}
......@@ -22,6 +22,7 @@ package org.jivesoftware.openfire.plugin;
import java.io.File;
import java.io.FileFilter;
import com.reucon.openfire.plugin.archive.xep0313.Xep0313Support1;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.archive.ArchiveIndexer;
import org.jivesoftware.openfire.archive.ArchiveInterceptor;
......@@ -73,6 +74,7 @@ public class MonitoringPlugin implements Plugin {
private IndexManager indexManager;
private Xep0136Support xep0136Support;
private Xep0313Support xep0313Support;
private Xep0313Support1 xep0313Support1;
public MonitoringPlugin() {
instance = this;
......@@ -162,6 +164,9 @@ public class MonitoringPlugin implements Plugin {
xep0313Support = new Xep0313Support(XMPPServer.getInstance());
xep0313Support.start();
xep0313Support1 = new Xep0313Support1(XMPPServer.getInstance());
xep0313Support1.start();
// Check if we Enterprise is installed and stop loading this plugin if
// found
File pluginDir = new File(JiveGlobals.getHomeDirectory(), "plugins");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment