Added support for HOXT (XEP-0332)

This is initial impementation of XEP-0332 (SMACK-552) -
HTTP over XMPP transport.
Created extensions, providers and unit tests.
Two features are missing: jingle and sipub.
This commit is contained in:
atsykholyas 2014-03-18 01:51:34 +02:00
parent fcc8414a92
commit 236ea71cee
20 changed files with 2052 additions and 1 deletions

View File

@ -12,6 +12,7 @@
<className>org.jivesoftware.smackx.ExtensionsProviderInitializer</className>
<className>org.jivesoftware.smackx.ExtensionsStartupClasses</className>
<className>org.jivesoftware.smackx.ExperimentalProviderInitializer</className>
<className>org.jivesoftware.smackx.ExperimentalStartupClasses</className>
<className>org.jivesoftware.smackx.WorkgroupProviderInitializer</className>
<className>org.jivesoftware.smackx.LegacyProviderInitializer</className>
</optionalStartupClasses>

View File

@ -0,0 +1,156 @@
<html>
<head>
<title>HTTP over XMPP transport</title>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div class="header">HTTP over XMPP transport</div><p>
Allows to transport HTTP communication over XMPP peer-to-peer networks.<p>
<ul>
<li><a href="#disco">Discover HOXT support</a></li>
<li><a href="#iqexchange">IQ exchange</a></li>
</ul>
<hr>
<div class="subheader"><a name="disco">Discover HOXT support</a></div><p>
<b>Description</b><p>
Before using this extension you must ensure that your counterpart supports it also.</p>
<b>Usage</b><p>
<p>Once you have your <i><b>ServiceDiscoveryManager</b></i> you will be able to discover information associated with
an XMPP entity. To discover the information of a given XMPP entity send <b>discoverInfo(entityID)</b>
to your <i><b>ServiceDiscoveryManager</b></i> where entityID is the ID of the entity. The message
<b>discoverInfo(entityID)</b> will answer an instance of <i><b>DiscoverInfo</b></i> that contains
the discovered information.</p>
<b>Examples</b><p>
In this example we can see how to check if the counterpart supports HOXT: <br>
<blockquote>
<pre> <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font>
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
<font color="#3f7f5f">// Get the information of a given XMPP entity</font>
DiscoverInfo discoInfo = discoManager.discoverInfo("juliet@capulet.com");
<font color="#3f7f5f">// Check if room is HOXT is supported</font>
discoInfo.containsFeature("urn:xmpp:http");
</pre>
</blockquote>
<hr>
<div class="subheader"><a name="iqexchange">IQ exchange</a></div><p>
<b>Description</b><p>
You can use IQ's to perform HTTP requests and responses.
This is applicable to relatively short requests and responses (due to limitation of XMPP message size).</p>
<b>Usage</b><p>
<p>First you need to register a <i><b>PacketListener</b></i> to be able to handle intended IQs.<p>
For the HTTP client you:
<ul>
<li>You create and send <i><b>HttpOverXmppReq</b></i> request.</li>
<li>Then you handle the <i><b>HttpOverXmppResp</b></i> response in your <i><b>PacketListener</b></i>.</li>
</ul>
For the HTTP server you:
<ul>
<li>You handle the <i><b>HttpOverXmppReq</b></i> requests in your <i><b>PacketListener</b></i>.</li>
<li>And create and send <i><b>HttpOverXmppResp</b></i> responses.</li>
</ul>
</p>
<b>Examples</b><p>
In this example we are HTTP client, so we send request (POST) and handle the response: <br>
<blockquote>
<pre> <font color="#3f7f5f">// register listener for IQ packets</font>
connection.addPacketListener(new IqPacketListener(), new PacketTypeFilter(IQ.class));
<font color="#3f7f5f">// create a request body</font>
String urlEncodedMessage = "I_love_you";
<font color="#3f7f5f">// create request</font>
HttpOverXmppReq.Req req = new HttpOverXmppReq.Req(HttpMethod.POST, "/mailbox");
req.setVersion("1.1");
<font color="#3f7f5f">// prepare headers</font>
Set&lt;Header&gt; set = new HashSet&lt;Header&gt;();
set.add(new Header("Host", "juliet.capulet.com"));
set.add(new Header("Content-Type", "application/x-www-form-urlencoded"));
set.add(new Header("Content-Length", Integer.toString(urlEncodedMessage.length())));
req.setHeaders(new HeadersExtension(set));
<font color="#3f7f5f">// provide body or request (not mandatory, - empty body is used for GET)</font>
AbstractHttpOverXmpp.Text child = new AbstractHttpOverXmpp.Text(urlEncodedMessage);
AbstractHttpOverXmpp.Data data = new AbstractHttpOverXmpp.Data(child);
req.setData(data);
<font color="#3f7f5f">// create IQ packet</font>
HttpOverXmppReq packet = new HttpOverXmppReq();
packet.setReq(req);
packet.setTo("juliet@capulet.com/balcony");
packet.setType(IQ.Type.SET);
packet.setPacketID("42");
<font color="#3f7f5f">// send it</font>
connection.sendPacket(packet);
<font color="#3f7f5f">// then in your PacketListener</font>
private class IqPacketListener implements PacketListener {
@Override
public void processPacket(Packet packet) {
IQ iq = (IQ) packet;
<font color="#3f7f5f">// verify from and packed ID</font>
if (iq.getFrom().equals("juliet@capulet.com/balcony") && (iq.getPacketID().equals("42"))) {
<font color="#3f7f5f">// ensure it's not ERROR</font>
if (iq.getType().equals(IQ.Type.RESULT)) {
<font color="#3f7f5f">// check if correct IQ implementation arrived</font>
if (iq instanceof HttpOverXmppResp) {
HttpOverXmppResp resp = (HttpOverXmppResp) iq;
<font color="#3f7f5f">// check HTTP response code</font>
if (resp.getResp().getStatusCode() == 200) {
<font color="#3f7f5f">// get content of the response</font>
AbstractHttpOverXmpp.DataChild child = resp.getResp().getData().getChild();
<font color="#3f7f5f">// check which type of content of the response arrived</font>
if (child instanceof AbstractHttpOverXmpp.Xml) {
<font color="#3f7f5f">// print the message and anxiously read if from console ;)</font>
System.out.println(((AbstractHttpOverXmpp.Xml) child).getText());
} else {
<font color="#3f7f5f">// process other AbstractHttpOverXmpp.DataChild subtypes</font>
}
}
}
}
}
}
}
</pre>
</blockquote>
<hr>
</body>
</html>

View File

@ -85,6 +85,11 @@
<td><a href="http://www.xmpp.org/extensions/xep-0016.html">XEP-0016</a></td>
<td>Enabling or disabling communication with other entities.</td>
</tr>
<tr>
<td><a href="hoxt.html">HTTP over XMPP transport</a></td>
<td><a href="http://www.xmpp.org/extensions/xep-0332.html">XEP-0332</a></td>
<td>Allows to transport HTTP communication over XMPP peer-to-peer networks.</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,54 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.initializer.SmackInitializer;
import org.jivesoftware.smack.util.FileUtils;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* {@link SmackInitializer} implementation for experimental module.
*/
public class ExperimentalStartupClasses implements SmackInitializer {
private static final String EXTENSIONS_XML = "classpath:org.jivesoftware.smackx/extensions.xml";
private List<Exception> exceptions = new LinkedList<Exception>();
// TODO log
@Override
public void initialize() {
InputStream is;
try {
is = FileUtils.getStreamForUrl(EXTENSIONS_XML, null);
SmackConfiguration.processConfigFile(is, exceptions);;
}
catch (Exception e) {
exceptions.add(e);
}
}
@Override
public List<Exception> getExceptions() {
return Collections.unmodifiableList(exceptions);
}
}

View File

@ -0,0 +1,61 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
/**
* Manager for HTTP ove XMPP transport (XEP-0332) extension.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public class HOXTManager {
/**
* Namespace for this extension.
*/
public static final String NAMESPACE = "urn:xmpp:http";
static {
XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
}
});
}
/**
* Returns true if the given entity understands the HTTP ove XMPP transport format and allows the exchange of such.
*
* @param jid jid
* @param connection connection
* @return true if the given entity understands the HTTP ove XMPP transport format and exchange.
* @throws XMPPErrorException
* @throws NoResponseException
* @throws NotConnectedException
*/
public static boolean isSupported(String jid, XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException {
return ServiceDiscoveryManager.getInstanceFor(connection).supportsFeature(jid, NAMESPACE);
}
}

View File

@ -0,0 +1,361 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.packet;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.shim.packet.HeadersExtension;
/**
* Abstract parent for Req and Resp IQ packets.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public abstract class AbstractHttpOverXmpp extends IQ {
/**
* Abstract representation of parent of Req and Resp elements.
*/
public static abstract class AbstractBody {
private HeadersExtension headers;
private Data data;
protected String version;
/**
* Returns string containing xml representation of this object.
*
* @return xml representation of this object
*/
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append(getStartTag());
builder.append(headers.toXML());
builder.append(data.toXML());
builder.append(getEndTag());
return builder.toString();
}
/**
* Returns start tag.
*
* @return start tag
*/
protected abstract String getStartTag();
/**
* Returns end tag.
*
* @return end tag
*/
protected abstract String getEndTag();
/**
* Returns version attribute.
*
* @return version attribute
*/
public String getVersion() {
return version;
}
/**
* Sets version attribute.
*
* @param version version attribute
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Returns Headers element.
*
* @return Headers element
*/
public HeadersExtension getHeaders() {
return headers;
}
/**
* Sets Headers element.
*
* @param headers Headers element
*/
public void setHeaders(HeadersExtension headers) {
this.headers = headers;
}
/**
* Returns Data element.
*
* @return Data element
*/
public Data getData() {
return data;
}
/**
* Sets Data element.
*
* @param data Headers element
*/
public void setData(Data data) {
this.data = data;
}
}
/**
* Representation of Data element.<p>
* This class is immutable.
*/
public static class Data {
private final DataChild child;
/**
* Creates Data element.
*
* @param child element nested by Data
*/
public Data(DataChild child) {
this.child = child;
}
/**
* Returns string containing xml representation of this object.
*
* @return xml representation of this object
*/
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append("<data>");
builder.append(child.toXML());
builder.append("</data>");
return builder.toString();
}
/**
* Returns element nested by Data.
*
* @return element nested by Data
*/
public DataChild getChild() {
return child;
}
}
/**
* Interface for child elements of Data element.
*/
public static interface DataChild {
/**
* Returns string containing xml representation of this object.
*
* @return xml representation of this object
*/
public String toXML();
}
/**
* Representation of Text element.<p>
* This class is immutable.
*/
public static class Text implements DataChild {
private final String text;
/**
* Creates this element.
*
* @param text value of text
*/
public Text(String text) {
this.text = text;
}
@Override
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append("<text>");
if (text != null) {
builder.append(text);
}
builder.append("</text>");
return builder.toString();
}
/**
* Returns text of this element.
*
* @return text
*/
public String getText() {
return text;
}
}
/**
* Representation of Base64 element.<p>
* This class is immutable.
*/
public static class Base64 implements DataChild {
private final String text;
/**
* Creates this element.
*
* @param text value of text
*/
public Base64(String text) {
this.text = text;
}
@Override
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append("<base64>");
if (text != null) {
builder.append(text);
}
builder.append("</base64>");
return builder.toString();
}
/**
* Returns text of this element.
*
* @return text
*/
public String getText() {
return text;
}
}
/**
* Representation of Xml element.<p>
* This class is immutable.
*/
public static class Xml implements DataChild {
private final String text;
/**
* Creates this element.
*
* @param text value of text
*/
public Xml(String text) {
this.text = text;
}
@Override
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append("<xml>");
if (text != null) {
builder.append(text);
}
builder.append("</xml>");
return builder.toString();
}
/**
* Returns text of this element.
*
* @return text
*/
public String getText() {
return text;
}
}
/**
* Representation of ChunkedBase64 element.<p>
* This class is immutable.
*/
public static class ChunkedBase64 implements DataChild {
private final String streamId;
/**
* Creates ChunkedBase86 element.
*
* @param streamId streamId attribute
*/
public ChunkedBase64(String streamId) {
this.streamId = streamId;
}
@Override
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append("<chunkedBase64 streamId='");
builder.append(streamId);
builder.append("'/>");
return builder.toString();
}
/**
* Returns streamId attribute.
*
* @return streamId attribute
*/
public String getStreamId() {
return streamId;
}
}
/**
* Representation of Ibb element.<p>
* This class is immutable.
*/
public static class Ibb implements DataChild {
private final String sid;
/**
* Creates Ibb element.
*
* @param sid sid attribute
*/
public Ibb(String sid) {
this.sid = sid;
}
@Override
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append("<ibb sid='");
builder.append(sid);
builder.append("'/>");
return builder.toString();
}
/**
* Returns sid attribute.
*
* @return sid attribute
*/
public String getSid() {
return sid;
}
}
}

View File

@ -0,0 +1,111 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.hoxt.HOXTManager;
/**
* Packet extension for base64 binary chunks.<p>
* This class is immutable.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public class Base64BinaryChunk implements PacketExtension {
public static final String ELEMENT_CHUNK = "chunk";
public static final String ATTRIBUTE_STREAM_ID = "streamId";
public static final String ATTRIBUTE_LAST = "last";
private final String streamId;
private final boolean last;
private final String text;
/**
* Creates the extension.
*
* @param text value of text attribute
* @param streamId value of streamId attribute
* @param last value of last attribute
*/
public Base64BinaryChunk(String text, String streamId, boolean last) {
this.text = text;
this.streamId = streamId;
this.last = last;
}
/**
* Creates the extension. Last attribute will be initialized with default value (false).
*
* @param text value of text attribute
* @param streamId value of streamId attribute
*/
public Base64BinaryChunk(String text, String streamId) {
this(text, streamId, false);
}
/**
* Returns streamId attribute.
*
* @return streamId attribute
*/
public String getStreamId() {
return streamId;
}
/**
* Returns last attribute.
*
* @return last attribute
*/
public boolean isLast() {
return last;
}
/**
* Returns text attribute.
*
* @return text attribute
*/
public String getText() {
return text;
}
@Override
public String getElementName() {
return ELEMENT_CHUNK;
}
@Override
public String getNamespace() {
return HOXTManager.NAMESPACE;
}
@Override
public String toXML() {
StringBuilder builder = new StringBuilder();
builder.append("<chunk xmlns='urn:xmpp:http' streamId='");
builder.append(streamId);
builder.append("' last='");
builder.append(Boolean.toString(last));
builder.append("'>");
builder.append(text);
builder.append("</chunk>");
return builder.toString();
}
}

View File

@ -0,0 +1,34 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.packet;
/**
* Enum containing HTTP methods.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public enum HttpMethod {
OPTIONS,
GET,
HEAD,
POST,
PUT,
DELETE,
TRACE,
PATCH
}

View File

@ -0,0 +1,203 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.packet;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.hoxt.HOXTManager;
/**
* Represents Req IQ packet.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public class HttpOverXmppReq extends AbstractHttpOverXmpp {
private Req req;
@Override
public String getChildElementXML() {
return req.toXML();
}
/**
* Returns Req element.
*
* @return Req element
*/
public Req getReq() {
return req;
}
/**
* Sets Req element.
*
* @param req Req element
*/
public void setReq(Req req) {
this.req = req;
}
/**
* Represents Req element.
*/
public static class Req extends AbstractBody {
private HttpMethod method;
private String resource;
// TODO: validate: xs:minInclusive value='256' xs:maxInclusive value='65536'
private int maxChunkSize = 0; // 0 means not set
private boolean sipub = true;
private boolean ibb = true;
private boolean jingle = true;
/**
* Creates this object.
*
* @param method method attribute
* @param resource resource attribute
*/
public Req(HttpMethod method, String resource) {
this.method = method;
this.resource = resource;
}
@Override
protected String getStartTag() {
StringBuilder builder = new StringBuilder();
builder.append("<req");
builder.append(" ");
builder.append("xmlns='").append(HOXTManager.NAMESPACE).append("'");
builder.append(" ");
builder.append("method='").append(method.toString()).append("'");
builder.append(" ");
builder.append("resource='").append(StringUtils.escapeForXML(resource)).append("'");
builder.append(" ");
builder.append("version='").append(StringUtils.escapeForXML(version)).append("'");
if (maxChunkSize != 0) {
builder.append(" ");
builder.append("maxChunkSize='").append(Integer.toString(maxChunkSize)).append("'");
}
builder.append(" ");
builder.append("sipub='").append(Boolean.toString(sipub)).append("'");
builder.append(" ");
builder.append("ibb='").append(Boolean.toString(ibb)).append("'");
builder.append(" ");
builder.append("jingle='").append(Boolean.toString(jingle)).append("'");
builder.append(">");
return builder.toString();
}
@Override
protected String getEndTag() {
return "</req>";
}
/**
* Returns method attribute.
*
* @return method attribute
*/
public HttpMethod getMethod() {
return method;
}
/**
* Returns resource attribute.
*
* @return resource attribute
*/
public String getResource() {
return resource;
}
/**
* Returns maxChunkSize attribute.
*
* @return maxChunkSize attribute
*/
public int getMaxChunkSize() {
return maxChunkSize;
}
/**
* Sets maxChunkSize attribute.
*
* @param maxChunkSize maxChunkSize attribute
*/
public void setMaxChunkSize(int maxChunkSize) {
this.maxChunkSize = maxChunkSize;
}
/**
* Returns sipub attribute.
*
* @return sipub attribute
*/
public boolean isSipub() {
return sipub;
}
/**
* Sets sipub attribute.
*
* @param sipub sipub attribute
*/
public void setSipub(boolean sipub) {
this.sipub = sipub;
}
/**
* Returns ibb attribute.
*
* @return ibb attribute
*/
public boolean isIbb() {
return ibb;
}
/**
* Sets ibb attribute.
*
* @param ibb ibb attribute
*/
public void setIbb(boolean ibb) {
this.ibb = ibb;
}
/**
* Returns jingle attribute.
*
* @return jingle attribute
*/
public boolean isJingle() {
return jingle;
}
/**
* Sets jingle attribute.
*
* @param jingle jingle attribute
*/
public void setJingle(boolean jingle) {
this.jingle = jingle;
}
}
}

View File

@ -0,0 +1,122 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.packet;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.hoxt.HOXTManager;
/**
* Represents Resp IQ packet.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public class HttpOverXmppResp extends AbstractHttpOverXmpp {
private Resp resp;
@Override
public String getChildElementXML() {
return resp.toXML();
}
/**
* Returns Resp element.
*
* @return Resp element
*/
public Resp getResp() {
return resp;
}
/**
* Sets Resp element.
*
* @param resp Resp element
*/
public void setResp(Resp resp) {
this.resp = resp;
}
/**
* Represents Resp element.
*/
public static class Resp extends AbstractBody {
private int statusCode;
private String statusMessage = null;
@Override
protected String getStartTag() {
StringBuilder builder = new StringBuilder();
builder.append("<resp");
builder.append(" ");
builder.append("xmlns='").append(HOXTManager.NAMESPACE).append("'");
builder.append(" ");
builder.append("version='").append(StringUtils.escapeForXML(version)).append("'");
builder.append(" ");
builder.append("statusCode='").append(Integer.toString(statusCode)).append("'");
if (statusMessage != null) {
builder.append(" ");
builder.append("statusMessage='").append(StringUtils.escapeForXML(statusMessage)).append("'");
}
builder.append(">");
return builder.toString();
}
@Override
protected String getEndTag() {
return "</resp>";
}
/**
* Returns statusCode attribute.
*
* @return statusCode attribute
*/
public int getStatusCode() {
return statusCode;
}
/**
* Sets statusCode attribute.
*
* @param statusCode statusCode attribute
*/
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
/**
* Returns statusMessage attribute.
*
* @return statusMessage attribute
*/
public String getStatusMessage() {
return statusMessage;
}
/**
* Sets statusMessage attribute.
*
* @param statusMessage statusMessage attribute
*/
public void setStatusMessage(String statusMessage) {
this.statusMessage = statusMessage;
}
}
}

View File

@ -0,0 +1,298 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.hoxt.packet.AbstractHttpOverXmpp;
import org.jivesoftware.smackx.shim.packet.Header;
import org.jivesoftware.smackx.shim.packet.HeadersExtension;
import org.jivesoftware.smackx.shim.provider.HeaderProvider;
import org.xmlpull.v1.XmlPullParser;
import java.util.HashSet;
import java.util.Set;
/**
* Abstract parent for Req and Resp packet providers.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public abstract class AbstractHttpOverXmppProvider implements IQProvider {
private static final String ELEMENT_HEADERS = "headers";
private static final String ELEMENT_HEADER = "header";
private static final String ELEMENT_DATA = "data";
private static final String ELEMENT_TEXT = "text";
private static final String ELEMENT_BASE_64 = "base64";
private static final String ELEMENT_CHUNKED_BASE_64 = "chunkedBase64";
private static final String ELEMENT_XML = "xml";
static final String ELEMENT_IBB = "ibb";
static final String ELEMENT_SIPUB = "sipub";
static final String ELEMENT_JINGLE = "jingle";
private static final String ATTRIBUTE_STREAM_ID = "streamId";
private static final String ATTRIBUTE_SID = "sid";
static final String ATTRIBUTE_VERSION = "version";
/**
* Parses Headers and Data elements.
*
* @param parser parser
* @param elementName name of concrete implementation of this element
* @param body parent Body element
* @throws Exception if anything goes wrong
*/
protected void parseHeadersAndData(XmlPullParser parser, String elementName, AbstractHttpOverXmpp.AbstractBody body) throws Exception {
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(ELEMENT_HEADERS)) {
HeadersExtension headersExtension = parseHeaders(parser);
body.setHeaders(headersExtension);
} else if (parser.getName().endsWith(ELEMENT_DATA)) {
AbstractHttpOverXmpp.Data data = parseData(parser);
body.setData(data);
} else {
throw new IllegalArgumentException("unexpected tag:" + parser.getName() + "'");
}
} else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(elementName)) {
done = true;
}
}
}
}
private HeadersExtension parseHeaders(XmlPullParser parser) throws Exception {
HeaderProvider provider = new HeaderProvider();
Set<Header> set = new HashSet<Header>();
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(ELEMENT_HEADER)) {
Header header = (Header) provider.parseExtension(parser);
set.add(header);
}
} else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(ELEMENT_HEADERS)) {
done = true;
}
}
}
return new HeadersExtension(set);
}
private AbstractHttpOverXmpp.Data parseData(XmlPullParser parser) throws Exception {
AbstractHttpOverXmpp.DataChild child = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals(ELEMENT_TEXT)) {
child = parseText(parser);
} else if (parser.getName().equals(ELEMENT_BASE_64)) {
child = parseBase64(parser);
} else if (parser.getName().equals(ELEMENT_CHUNKED_BASE_64)) {
child = parseChunkedBase64(parser);
} else if (parser.getName().equals(ELEMENT_XML)) {
child = parseXml(parser);
} else if (parser.getName().equals(ELEMENT_IBB)) {
child = parseIbb(parser);
} else if (parser.getName().equals(ELEMENT_SIPUB)) {
// TODO: sipub is allowed by xep-0332, but is not implemented yet
throw new UnsupportedOperationException("sipub is not supported yet");
} else if (parser.getName().equals(ELEMENT_JINGLE)) {
// TODO: jingle is allowed by xep-0332, but is not implemented yet
throw new UnsupportedOperationException("jingle is not supported yet");
} else {
// other elements are not allowed
throw new IllegalArgumentException("unsupported child tag: " + parser.getName());
}
} else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(ELEMENT_DATA)) {
done = true;
}
}
}
AbstractHttpOverXmpp.Data data = new AbstractHttpOverXmpp.Data(child);
return data;
}
private AbstractHttpOverXmpp.Text parseText(XmlPullParser parser) throws Exception {
String text = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(ELEMENT_TEXT)) {
done = true;
} else {
throw new IllegalArgumentException("unexpected end tag of: " + parser.getName());
}
} else if (eventType == XmlPullParser.TEXT) {
text = parser.getText();
} else {
throw new IllegalArgumentException("unexpected eventType: " + eventType);
}
}
return new AbstractHttpOverXmpp.Text(text);
}
private AbstractHttpOverXmpp.Xml parseXml(XmlPullParser parser) throws Exception {
StringBuilder builder = new StringBuilder();
boolean done = false;
boolean startClosed = true;
while (!done) {
int eventType = parser.next();
if ((eventType == XmlPullParser.END_TAG) && parser.getName().equals(ELEMENT_XML)) {
done = true;
} else { // just write everything else as text
if (eventType == XmlPullParser.START_TAG) {
if (!startClosed) {
builder.append('>');
}
builder.append('<');
builder.append(parser.getName());
appendXmlAttributes(parser, builder);
startClosed = false;
} else if (eventType == XmlPullParser.END_TAG) {
if (startClosed) {
builder.append("</");
builder.append(parser.getName());
builder.append('>');
} else {
builder.append("/>");
startClosed = true;
}
} else if (eventType == XmlPullParser.TEXT) {
if (!startClosed) {
builder.append('>');
startClosed = true;
}
builder.append(StringUtils.escapeForXML(parser.getText()));
} else {
throw new IllegalArgumentException("unexpected eventType: " + eventType);
}
}
}
return new AbstractHttpOverXmpp.Xml(builder.toString());
}
private void appendXmlAttributes(XmlPullParser parser, StringBuilder builder) throws Exception {
// NOTE: for now we ignore namespaces
int count = parser.getAttributeCount();
if (count > 0) {
for (int i = 0; i < count; i++) {
builder.append(' ');
builder.append(parser.getAttributeName(i));
builder.append("=\"");
builder.append(StringUtils.escapeForXML(parser.getAttributeValue(i)));
builder.append('"');
}
}
}
private AbstractHttpOverXmpp.Base64 parseBase64(XmlPullParser parser) throws Exception {
String text = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(ELEMENT_BASE_64)) {
done = true;
} else {
throw new IllegalArgumentException("unexpected end tag of: " + parser.getName());
}
} else if (eventType == XmlPullParser.TEXT) {
text = parser.getText();
} else {
throw new IllegalArgumentException("unexpected eventType: " + eventType);
}
}
return new AbstractHttpOverXmpp.Base64(text);
}
private AbstractHttpOverXmpp.ChunkedBase64 parseChunkedBase64(XmlPullParser parser) throws Exception {
String streamId = parser.getAttributeValue("", ATTRIBUTE_STREAM_ID);
AbstractHttpOverXmpp.ChunkedBase64 child = new AbstractHttpOverXmpp.ChunkedBase64(streamId);
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(ELEMENT_CHUNKED_BASE_64)) {
done = true;
} else {
throw new IllegalArgumentException("unexpected end tag: " + parser.getName());
}
} else {
throw new IllegalArgumentException("unexpected event type: " + eventType);
}
}
return child;
}
private AbstractHttpOverXmpp.Ibb parseIbb(XmlPullParser parser) throws Exception {
String sid = parser.getAttributeValue("", ATTRIBUTE_SID);
AbstractHttpOverXmpp.Ibb child = new AbstractHttpOverXmpp.Ibb(sid);
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(ELEMENT_IBB)) {
done = true;
} else {
throw new IllegalArgumentException("unexpected end tag: " + parser.getName());
}
} else {
throw new IllegalArgumentException("unexpected event type: " + eventType);
}
}
return child;
}
}

View File

@ -0,0 +1,69 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.hoxt.packet.Base64BinaryChunk;
import org.xmlpull.v1.XmlPullParser;
/**
* Packet provider for base64 binary chunks.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public class Base64BinaryChunkProvider implements PacketExtensionProvider {
/**
* Required no-argument constructor.
*/
public Base64BinaryChunkProvider() {
}
@Override
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
String streamId = parser.getAttributeValue("", Base64BinaryChunk.ATTRIBUTE_STREAM_ID);
String lastString = parser.getAttributeValue("", Base64BinaryChunk.ATTRIBUTE_LAST);
boolean last = false;
if (lastString != null) {
last = Boolean.parseBoolean(lastString);
}
String text = null;
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(Base64BinaryChunk.ELEMENT_CHUNK)) {
done = true;
} else {
throw new IllegalArgumentException("unexpected end tag of: " + parser.getName());
}
} else if (eventType == XmlPullParser.TEXT) {
text = parser.getText();
} else {
throw new IllegalArgumentException("unexpected eventType: " + eventType);
}
}
return new Base64BinaryChunk(text, streamId, last);
}
}

View File

@ -0,0 +1,87 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.hoxt.packet.HttpMethod;
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppReq;
import org.xmlpull.v1.XmlPullParser;
/**
* Req packet provider.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public class HttpOverXmppReqProvider extends AbstractHttpOverXmppProvider {
private static final String ELEMENT_REQ = "req";
private static final String ATTRIBUTE_METHOD = "method";
private static final String ATTRIBUTE_RESOURCE = "resource";
private static final String ATTRIBUTE_MAX_CHUNK_SIZE = "maxChunkSize";
/**
* Mandatory no argument constructor.
*/
public HttpOverXmppReqProvider() {
}
@Override
public IQ parseIQ(XmlPullParser parser) throws Exception {
String method = parser.getAttributeValue("", ATTRIBUTE_METHOD);
String resource = parser.getAttributeValue("", ATTRIBUTE_RESOURCE);
String version = parser.getAttributeValue("", ATTRIBUTE_VERSION);
String maxChunkSize = parser.getAttributeValue("", ATTRIBUTE_MAX_CHUNK_SIZE);
HttpMethod reqMethod = HttpMethod.valueOf(method);
HttpOverXmppReq.Req req = new HttpOverXmppReq.Req(reqMethod, resource);
req.setVersion(version);
Boolean sipub = true;
Boolean jingle = true;
Boolean ibb = true;
String sipubStr = parser.getAttributeValue("", AbstractHttpOverXmppProvider.ELEMENT_SIPUB);
String ibbStr = parser.getAttributeValue("", AbstractHttpOverXmppProvider.ELEMENT_IBB);
String jingleStr = parser.getAttributeValue("", AbstractHttpOverXmppProvider.ELEMENT_JINGLE);
if (sipubStr != null) {
sipub = Boolean.valueOf(sipubStr);
}
if (ibbStr != null) {
ibb = Boolean.valueOf(ibbStr);
}
if (jingleStr != null) {
jingle = Boolean.valueOf(jingleStr);
}
req.setIbb(ibb);
req.setSipub(sipub);
req.setJingle(jingle);
if (maxChunkSize != null) {
int maxChunkSizeValue = Integer.parseInt(maxChunkSize);
req.setMaxChunkSize(maxChunkSizeValue);
}
parseHeadersAndData(parser, ELEMENT_REQ, req);
HttpOverXmppReq packet = new HttpOverXmppReq();
packet.setReq(req);
return packet;
}
}

View File

@ -0,0 +1,60 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppResp;
import org.xmlpull.v1.XmlPullParser;
/**
* Resp packet provider.
*
* @author Andriy Tsykholyas
* @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
*/
public class HttpOverXmppRespProvider extends AbstractHttpOverXmppProvider {
private static final String ELEMENT_RESP = "resp";
private static final String ATTRIBUTE_STATUS_MESSAGE = "statusMessage";
private static final String ATTRIBUTE_STATUS_CODE = "statusCode";
/**
* Mandatory no argument constructor.
*/
public HttpOverXmppRespProvider() {
}
@Override
public IQ parseIQ(XmlPullParser parser) throws Exception {
String version = parser.getAttributeValue("", ATTRIBUTE_VERSION);
String statusMessage = parser.getAttributeValue("", ATTRIBUTE_STATUS_MESSAGE);
String statusCodeString = parser.getAttributeValue("", ATTRIBUTE_STATUS_CODE);
int statusCode = Integer.parseInt(statusCodeString);
HttpOverXmppResp.Resp resp = new HttpOverXmppResp.Resp();
resp.setVersion(version);
resp.setStatusMessage(statusMessage);
resp.setStatusCode(statusCode);
parseHeadersAndData(parser, ELEMENT_RESP, resp);
HttpOverXmppResp packet = new HttpOverXmppResp();
packet.setResp(resp);
return packet;
}
}

View File

@ -1,6 +1,23 @@
<?xml version="1.0"?>
<!-- Providers for workgroup extensions -->
<smackProviders>
<smackProviders>
<!-- XEP-0332 HTTP over XMPP transport -->
<iqProvider>
<elementName>req</elementName>
<namespace>urn:xmpp:http</namespace>
<className>org.jivesoftware.smackx.hoxt.provider.HttpOverXmppReqProvider</className>
</iqProvider>
<iqProvider>
<elementName>resp</elementName>
<namespace>urn:xmpp:http</namespace>
<className>org.jivesoftware.smackx.hoxt.provider.HttpOverXmppRespProvider</className>
</iqProvider>
<extensionProvider>
<elementName>chunk</elementName>
<namespace>urn:xmpp:http</namespace>
<className>org.jivesoftware.smackx.hoxt.provider.Base64BinaryChunkProvider</className>
</extensionProvider>
<!-- XEP-0280 Message Carbons -->
<extensionProvider>

View File

@ -0,0 +1,5 @@
<smack>
<startupClasses>
<className>org.jivesoftware.smackx.hoxt.HOXTManager</className>
</startupClasses>
</smack>

View File

@ -0,0 +1,203 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.hoxt.packet.AbstractHttpOverXmpp;
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppReq;
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppResp;
import org.jivesoftware.smackx.shim.packet.Header;
import org.jivesoftware.smackx.shim.packet.HeadersExtension;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests correct headers and data parsing in 'req' and 'resp' elements.
*/
public class AbstractHttpOverXmppProviderTest {
@Test
public void areRespHeadersParsedCorrectly() throws Exception {
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200' statusMessage='OK'>"
+ "<headers xmlns='http://jabber.org/protocol/shim'>"
+ "<header name='Date'>Fri, 03 May 2013 13:52:10 GMT-4</header>"
+ "<header name='Allow'>OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE</header>"
+ "<header name='Content-Length'>0</header>"
+ "</headers>"
+ "</resp>";
Map<String, String> expectedHeaders = new HashMap<String, String>();
expectedHeaders.put("Date", "Fri, 03 May 2013 13:52:10 GMT-4");
expectedHeaders.put("Allow", "OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE");
expectedHeaders.put("Content-Length", "0");
AbstractHttpOverXmppProvider provider = new HttpOverXmppRespProvider();
XmlPullParser parser = TestUtils.getParser(string, "resp");
IQ iq = provider.parseIQ(parser);
assertTrue(iq instanceof HttpOverXmppResp);
AbstractHttpOverXmpp.AbstractBody body = ((HttpOverXmppResp) iq).getResp();
checkHeaders(body.getHeaders(), expectedHeaders);
}
@Test
public void areReqHeadersParsedCorrectly() throws Exception {
String string = "<req xmlns='urn:xmpp:http' method='GET' resource='/rdf/xep' version='1.1'>"
+ "<headers xmlns='http://jabber.org/protocol/shim'>"
+ "<header name='Host'>clayster.com</header>"
+ "</headers>"
+ "</req>";
Map<String, String> expectedHeaders = new HashMap<String, String>();
expectedHeaders.put("Host", "clayster.com");
AbstractHttpOverXmppProvider provider = new HttpOverXmppReqProvider();
XmlPullParser parser = TestUtils.getParser(string, "req");
IQ iq = provider.parseIQ(parser);
assertTrue(iq instanceof HttpOverXmppReq);
AbstractHttpOverXmpp.AbstractBody body = ((HttpOverXmppReq) iq).getReq();
checkHeaders(body.getHeaders(), expectedHeaders);
}
@Test
public void isTextDataParsedCorrectly() throws Exception {
String expectedText = "@prefix dc: <http://purl.org/dc/elements/1.1/>."
+ "@base <http://clayster.com/>."
+ "<xep> dc:title \"HTTP over XMPP\";"
+ "dc:creator <PeterWaher>;"
+ "dc:publisher <XSF>.";
String encodedText = "@prefix dc: &lt;http://purl.org/dc/elements/1.1/&gt;."
+ "@base &lt;http://clayster.com/&gt;."
+ "&lt;xep&gt; dc:title \"HTTP over XMPP\";"
+ "dc:creator &lt;PeterWaher&gt;;"
+ "dc:publisher &lt;XSF&gt;.";
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200' statusMessage='OK'>"
+ "<headers xmlns='http://jabber.org/protocol/shim'><header name='Server'>Clayster</header></headers>"
+ "<data><text>"
+ encodedText
+ "</text></data></resp>";
AbstractHttpOverXmpp.Text text = (AbstractHttpOverXmpp.Text) parseAbstractBody(
string, "resp").getData().getChild();
assertEquals(expectedText, text.getText());
}
@Test
public void isXmlDataParsedCorrectly() throws Exception {
String expectedXml = "<sparql><head><variable name=\"title\"/><variable name=\"creator\"/>" // no xmlns here
+ "</head><results><result>"
+ "<binding name=\"title\">"
+ "<literal>HTTP over XMPP</literal>"
+ "</binding>"
+ "<binding name=\"creator\">"
+ "<uri>http://clayster.com/PeterWaher</uri>"
+ "</binding>"
+ "</result>"
+ "</results>"
+ "</sparql>";
String encodedXml = "<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\"><head><variable name=\"title\"/><variable name=\"creator\"/>"
+ "</head><results><result>"
+ "<binding name=\"title\">"
+ "<literal>HTTP over XMPP</literal>"
+ "</binding>"
+ "<binding name=\"creator\">"
+ "<uri>http://clayster.com/PeterWaher</uri>"
+ "</binding>"
+ "</result>"
+ "</results>"
+ "</sparql>";
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200' statusMessage='OK'>"
+ "<headers xmlns='http://jabber.org/protocol/shim'><header name='Server'>Clayster</header></headers>"
+ "<data><xml>"
+ encodedXml
+ "</xml></data></resp>";
AbstractHttpOverXmpp.Xml xmlProviderValue = (AbstractHttpOverXmpp.Xml) parseAbstractBody(
string, "resp").getData().getChild();
assertEquals(expectedXml, xmlProviderValue.getText());
}
@Test
public void isBase64DataParsedCorrectly() throws Exception {
String base64Data = "iVBORw0KGgoAAAANSUhEUgAAASwAAAGQCAYAAAAUdV17AAAAAXNSR0 ... tVWJd+e+y1AAAAABJRU5ErkJggg==";
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200' statusMessage='OK'>"
+ "<headers xmlns='http://jabber.org/protocol/shim'><header name='Server'>Clayster</header></headers>"
+ "<data><base64>"
+ base64Data
+ "</base64></data></resp>";
AbstractHttpOverXmpp.Base64 base64ProviderValue = (AbstractHttpOverXmpp.Base64) parseAbstractBody(
string, "resp").getData().getChild();
assertEquals(base64Data, base64ProviderValue.getText());
}
@Test
public void isChunkedBase64DataParsedCorrectly() throws Exception {
String streamId = "Stream0001";
String chunkBase64Data = " <chunkedBase64 streamId='" + streamId + "'/>";
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200' statusMessage='OK'>"
+ "<headers xmlns='http://jabber.org/protocol/shim'><header name='Server'>Clayster</header></headers>"
+ "<data>"
+ chunkBase64Data
+ "</data></resp>";
AbstractHttpOverXmpp.ChunkedBase64 chunkedBase64Value = (AbstractHttpOverXmpp.ChunkedBase64) parseAbstractBody(
string, "resp").getData().getChild();
assertEquals(streamId, chunkedBase64Value.getStreamId());
}
@Test
public void isIbbDataParsedCorrectly() throws Exception {
String sid = "Stream0002";
String ibbData = " <ibb sid='" + sid + "'/>";
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200' statusMessage='OK'>"
+ "<headers xmlns='http://jabber.org/protocol/shim'><header name='Server'>Clayster</header></headers>"
+ "<data>"
+ ibbData
+ "</data></resp>";
AbstractHttpOverXmpp.Ibb ibbValue = (AbstractHttpOverXmpp.Ibb) parseAbstractBody(
string, "resp").getData().getChild();
assertEquals(sid, ibbValue.getSid());
}
private AbstractHttpOverXmpp.AbstractBody parseAbstractBody(String string, String tag) throws Exception {
AbstractHttpOverXmppProvider provider = new HttpOverXmppRespProvider();
XmlPullParser parser = TestUtils.getParser(string, tag);
IQ iq = provider.parseIQ(parser);
assertTrue(iq instanceof HttpOverXmppResp);
AbstractHttpOverXmpp.AbstractBody body = ((HttpOverXmppResp) iq).getResp();
return body;
}
private void checkHeaders(HeadersExtension headers, Map<String, String> expectedHeaders) {
Collection<Header> collection = headers.getHeaders();
assertEquals(collection.size(), expectedHeaders.size());
for (Header header : collection) {
assertTrue(expectedHeaders.containsKey(header.getName()));
assertEquals(expectedHeaders.get(header.getName()), header.getValue());
}
}
}

View File

@ -0,0 +1,65 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.hoxt.packet.Base64BinaryChunk;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
import static org.junit.Assert.*;
/**
* Tests correct parsing of 'chunk' elements in Message stanza.
*/
public class Base64BinaryChunkProviderTest {
@Test
public void isNonLatsChunkParsedCorrectly() throws Exception {
String base64Text = "iVBORw0KGgoAAAANSUhEUgAAASwAAAGQCAYAA";
String string = "<chunk xmlns='urn:xmpp:http' streamId='Stream0001'>" + base64Text + "</chunk>";
Base64BinaryChunkProvider provider = new Base64BinaryChunkProvider();
XmlPullParser parser = TestUtils.getParser(string, "chunk");
PacketExtension extension = provider.parseExtension(parser);
assertTrue(extension instanceof Base64BinaryChunk);
Base64BinaryChunk chunk = (Base64BinaryChunk) extension;
assertEquals("Stream0001", chunk.getStreamId());
assertFalse(chunk.isLast());
assertEquals(base64Text, chunk.getText());
}
@Test
public void isLatsChunkParsedCorrectly() throws Exception {
String base64Text = "2uPzi9u+tVWJd+e+y1AAAAABJRU5ErkJggg==";
String string = "<chunk xmlns='urn:xmpp:http' streamId='Stream0001' last='true'>" + base64Text + "</chunk>";
Base64BinaryChunkProvider provider = new Base64BinaryChunkProvider();
XmlPullParser parser = TestUtils.getParser(string, "chunk");
PacketExtension extension = provider.parseExtension(parser);
assertTrue(extension instanceof Base64BinaryChunk);
Base64BinaryChunk chunk = (Base64BinaryChunk) extension;
assertEquals("Stream0001", chunk.getStreamId());
assertTrue(chunk.isLast());
assertEquals(base64Text, chunk.getText());
}
}

View File

@ -0,0 +1,76 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.hoxt.packet.HttpMethod;
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppReq;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class HttpOverXmppReqProviderTest {
@Test
public void areAllReqAttributesCorrectlyParsed() throws Exception {
String string = "<req xmlns='urn:xmpp:http' method='OPTIONS' resource='*' version='1.1'/>";
HttpOverXmppReq.Req req = parseReq(string);
assertEquals(req.getVersion(), "1.1");
assertEquals(req.getMethod(), HttpMethod.OPTIONS);
assertEquals(req.getResource(), "*");
}
@Test
public void areGetRequestAttributesCorrectlyParsed() throws Exception {
String string = "<req xmlns='urn:xmpp:http' method='GET' resource='/rdf/xep' version='1.1'/>";
HttpOverXmppReq.Req req = parseReq(string);
assertEquals(req.getVersion(), "1.1");
assertEquals(req.getMethod(), HttpMethod.GET);
assertEquals(req.getResource(), "/rdf/xep");
}
@Test
public void getReqOptionAttributesCorrectlyParsed() throws Exception {
String string = "<req xmlns='urn:xmpp:http' method='OPTIONS' resource='*' version='1.1' maxChunkSize='256' sipub='false' ibb='true' jingle='false'/>";
HttpOverXmppReq.Req req = parseReq(string);
assertEquals(req.getMaxChunkSize(), 256);
assertEquals(req.isSipub(), false);
assertEquals(req.isIbb(), true);
assertEquals(req.isJingle(), false);
}
@Test
public void getReqOptionalAttributesDefaultValues() throws Exception {
String string = "<req xmlns='urn:xmpp:http' method='OPTIONS' resource='*' version='1.1'/>";
HttpOverXmppReq.Req req = parseReq(string);
assertEquals(req.isSipub(), true);
assertEquals(req.isIbb(), true);
assertEquals(req.isJingle(), true);
}
private HttpOverXmppReq.Req parseReq(String string) throws Exception {
HttpOverXmppReqProvider provider = new HttpOverXmppReqProvider();
XmlPullParser parser = TestUtils.getParser(string, "req");
IQ iq = provider.parseIQ(parser);
assertTrue(iq instanceof HttpOverXmppReq);
HttpOverXmppReq castedIq = (HttpOverXmppReq) iq;
return castedIq.getReq();
}
}

View File

@ -0,0 +1,63 @@
/**
*
* Copyright 2014 Andriy Tsykholyas
*
* Licensed 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.jivesoftware.smackx.hoxt.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.test.util.TestUtils;
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppResp;
import org.junit.Test;
import org.xmlpull.v1.XmlPullParser;
import static org.junit.Assert.*;
/**
* Tests correct attribute parsing in 'resp' element.
*/
public class HttpOverXmppRespProviderTest {
@Test
public void areAllRespAttributesCorrectlyParsed() throws Exception {
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200' statusMessage='OK'/>";
HttpOverXmppRespProvider provider = new HttpOverXmppRespProvider();
XmlPullParser parser = TestUtils.getParser(string, "resp");
IQ iq = provider.parseIQ(parser);
assertTrue(iq instanceof HttpOverXmppResp);
HttpOverXmppResp castedIq = (HttpOverXmppResp) iq;
HttpOverXmppResp.Resp resp = castedIq.getResp();
assertEquals(resp.getVersion(), "1.1");
assertEquals(resp.getStatusCode(), 200);
assertEquals(resp.getStatusMessage(), "OK");
}
@Test
public void areRespAttributesWothoutMessageCorrectlyParsed() throws Exception {
String string = "<resp xmlns='urn:xmpp:http' version='1.1' statusCode='200'/>";
HttpOverXmppRespProvider provider = new HttpOverXmppRespProvider();
XmlPullParser parser = TestUtils.getParser(string, "resp");
IQ iq = provider.parseIQ(parser);
assertTrue(iq instanceof HttpOverXmppResp);
HttpOverXmppResp castedIq = (HttpOverXmppResp) iq;
HttpOverXmppResp.Resp resp = castedIq.getResp();
assertEquals(resp.getVersion(), "1.1");
assertEquals(resp.getStatusCode(), 200);
assertNull(resp.getStatusMessage());
}
}