Mercury-IM/domain/src/main/java/org/jivesoftware/smackx/pubsub/PubSubUri.java

218 lines
6.8 KiB
Java

package org.jivesoftware.smackx.pubsub;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.jxmpp.util.XmppStringUtils;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
public class PubSubUri {
private static final Logger LOGGER = Logger.getLogger(PubSubUri.class.getName());
public static final String SCHEME = "xmpp";
public static final String NODE = ";node=";
public static final String ITEM = ";item=";
public static final String ACTION = ";action=";
public static final String PUBSUB = "pubsub";
private final BareJid service;
private final String node;
private final String item;
private final Action action;
public PubSubUri(BareJid service, String node, String item, Action action) {
this.service = service;
this.node = node;
this.item = item;
this.action = action;
}
public PubSubUri(URI uri) throws XmppStringprepException {
throwIfNoXmppUri(uri);
String ssp = uri.getSchemeSpecificPart();
service = getService(ssp);
node = getNodeOrNull(ssp);
item = getItemOrNull(ssp);
action = getActionOrNull(ssp);
}
/**
* Parse a {@link PubSubUri}.
* This method ignores unknown parameters.
*
* @param s string representation of the URI.
* @return parsed {@link PubSubUri}
* @throws XmppStringprepException if the string does not represent a valid XMPP PubSub URI.
*/
public static PubSubUri from(String s)
throws XmppStringprepException {
URI uri = URI.create(s);
return new PubSubUri(uri);
}
/**
* Return the {@link BareJid} of the referenced pubsub service.
* That may be a {@link org.jxmpp.jid.DomainBareJid} like 'pubsub.shakespeare.lit' or a
* {@link EntityBareJid} like 'hamlet@denmark.lit'.
*
* @return pubsub service jid
*/
public BareJid getService() {
return service;
}
/**
* Return the pubsub node name or null if not present.
*
* @return node name or null
*/
public String getNode() {
return node;
}
/**
* Return the pubsub item name or null if not present.
*
* @return item or null
*/
public String getItem() {
return item;
}
/**
* Return the {@link Action} of the {@link PubSubUri} or null if not present.
*
* @return action or null
*/
public Action getAction() {
return action;
}
/**
* Throw an {@link IllegalArgumentException} if the {@link URI URIs} scheme doesn't equal 'xmpp'.
*
* @param uri uri
*/
private static void throwIfNoXmppUri(URI uri) throws XmppStringprepException {
if (!uri.getScheme().equals(SCHEME)) {
throw new XmppStringprepException(uri.toString(), "URI is not of scheme 'xmpp'.");
}
}
/**
* Extract the {@link BareJid} of the pubsub service the URI is pointing to.
*
* @param ssp scheme specific part of the URI
* @return pubsub service jid
*
* @throws XmppStringprepException if the service jid is malformed.
*/
private static BareJid getService(String ssp) throws XmppStringprepException {
String serviceString = ssp.substring(0, ssp.indexOf("?"));
// TODO: Use JidCreate.bareFrom(serviceString) once jxmpp is bumped to > 1.0.0
// see https://github.com/igniterealtime/jxmpp/pull/22
throwIfFullJid(serviceString);
BareJid jid;
try {
jid = JidCreate.entityBareFromUrlEncoded(serviceString);
} catch (XmppStringprepException e) {
jid = JidCreate.domainBareFromUrlEncoded(serviceString);
}
return jid;
}
/**
* Throw an {@link XmppStringprepException} if the provided {@link String} represents a {@link org.jxmpp.jid.FullJid}.
*
* @param jid jid as string
* @throws XmppStringprepException if jid has resource part
*/
private static void throwIfFullJid(String jid) throws XmppStringprepException {
if (XmppStringUtils.isFullJID(jid)) {
throw new XmppStringprepException(jid, "FullJid not allowed here.");
}
}
/**
* Return the value of the 'node' key or null if not present.
* @param ssp scheme specific part
* @return node value or null
*/
private static String getNodeOrNull(String ssp) {
return getValueOrNull(ssp, NODE);
}
/**
* Return the value of the 'item' key or null if not present.
* @param ssp scheme specific part
* @return item value or null
*/
private static String getItemOrNull(String ssp) {
return getValueOrNull(ssp, ITEM);
}
/**
* Return the value of the 'action' key or null if not present.
* @param ssp scheme specific part
* @return action value or null
*/
private static Action getActionOrNull(String ssp) {
String actionString = getValueOrNull(ssp, ACTION);
if (actionString == null) {
return null;
}
if (!ssp.substring(ssp.indexOf("?") + 1).startsWith(PUBSUB)) {
LOGGER.log(Level.WARNING, "URI contains 'action', but does not have iquerytype set to '" + PUBSUB + "'.\n" +
"See https://tools.ietf.org/html/rfc5122#section-2.2 and https://xmpp.org/extensions/xep-0060.html#example-229 f.");
}
return Action.valueOf(actionString);
}
/**
* Returns the ivalue value of the ipair with ikey key.
*
* @see <a href="https://tools.ietf.org/html/rfc5122#section-2.2">ABNF in section 2.2 of rfc5122</a>
* @param ssp scheme specific part of the XMPP URI/IRI.
* @param key ikey
* @return ivalue
*/
private static String getValueOrNull(String ssp, String key) {
String args = ssp.substring(ssp.indexOf("?") + 1);
boolean hasKey = args.contains(key);
if (!hasKey) return null;
int keyPos = args.indexOf(key) + key.length();
int nextPos = args.indexOf(";", keyPos);
if (nextPos == -1) {
nextPos = args.length();
}
return args.substring(keyPos, nextPos);
}
@Override
public String toString() {
return SCHEME + ":" + getService() + "?" +
(getAction() != null ? PUBSUB + ACTION + getAction() : "") +
(getNode() != null ? NODE + getNode() : "") +
(getItem() != null ? ITEM + getItem() : "");
}
/**
* PubSub specific actions as defined in XEP-0060: Publish-Subscribe: §16.6 URI Query Types.
*
* @see <a href="https://xmpp.org/extensions/xep-0060.html#registrar-querytypes">Registered URI Query Types</a>
*/
public enum Action {
subscribe,
unsubscribe,
retrieve
}
}