From 236ea71ceeaacc910ccd9a6db202deaa8919c243 Mon Sep 17 00:00:00 2001 From: atsykholyas Date: Tue, 18 Mar 2014 01:51:34 +0200 Subject: [PATCH] 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. --- .../org.jivesoftware.smack/smack-config.xml | 1 + documentation/extensions/hoxt.html | 156 ++++++++ documentation/extensions/intro.html | 5 + .../smackx/ExperimentalStartupClasses.java | 54 +++ .../jivesoftware/smackx/hoxt/HOXTManager.java | 61 +++ .../hoxt/packet/AbstractHttpOverXmpp.java | 361 ++++++++++++++++++ .../smackx/hoxt/packet/Base64BinaryChunk.java | 111 ++++++ .../smackx/hoxt/packet/HttpMethod.java | 34 ++ .../smackx/hoxt/packet/HttpOverXmppReq.java | 203 ++++++++++ .../smackx/hoxt/packet/HttpOverXmppResp.java | 122 ++++++ .../AbstractHttpOverXmppProvider.java | 298 +++++++++++++++ .../provider/Base64BinaryChunkProvider.java | 69 ++++ .../provider/HttpOverXmppReqProvider.java | 87 +++++ .../provider/HttpOverXmppRespProvider.java | 60 +++ .../experimental.providers | 19 +- .../org.jivesoftware.smackx/extensions.xml | 5 + .../AbstractHttpOverXmppProviderTest.java | 203 ++++++++++ .../Base64BinaryChunkProviderTest.java | 65 ++++ .../provider/HttpOverXmppReqProviderTest.java | 76 ++++ .../HttpOverXmppRespProviderTest.java | 63 +++ 20 files changed, 2052 insertions(+), 1 deletion(-) create mode 100644 documentation/extensions/hoxt.html create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/ExperimentalStartupClasses.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/HOXTManager.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/Base64BinaryChunk.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpMethod.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppReq.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppResp.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProvider.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProvider.java create mode 100644 experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProvider.java create mode 100644 experimental/src/main/resources/org.jivesoftware.smackx/extensions.xml create mode 100644 experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProviderTest.java create mode 100644 experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProviderTest.java create mode 100644 experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProviderTest.java create mode 100644 experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProviderTest.java diff --git a/core/src/main/resources/org.jivesoftware.smack/smack-config.xml b/core/src/main/resources/org.jivesoftware.smack/smack-config.xml index 2b3540365..3c6484bb0 100644 --- a/core/src/main/resources/org.jivesoftware.smack/smack-config.xml +++ b/core/src/main/resources/org.jivesoftware.smack/smack-config.xml @@ -12,6 +12,7 @@ org.jivesoftware.smackx.ExtensionsProviderInitializer org.jivesoftware.smackx.ExtensionsStartupClasses org.jivesoftware.smackx.ExperimentalProviderInitializer + org.jivesoftware.smackx.ExperimentalStartupClasses org.jivesoftware.smackx.WorkgroupProviderInitializer org.jivesoftware.smackx.LegacyProviderInitializer diff --git a/documentation/extensions/hoxt.html b/documentation/extensions/hoxt.html new file mode 100644 index 000000000..96f3dd559 --- /dev/null +++ b/documentation/extensions/hoxt.html @@ -0,0 +1,156 @@ + + +HTTP over XMPP transport + + + + + +
HTTP over XMPP transport

+ +Allows to transport HTTP communication over XMPP peer-to-peer networks.

+ +

+ +
+ +
Discover HOXT support

+ +Description

+ +Before using this extension you must ensure that your counterpart supports it also.

+ +Usage

+ +

Once you have your ServiceDiscoveryManager you will be able to discover information associated with +an XMPP entity. To discover the information of a given XMPP entity send discoverInfo(entityID) +to your ServiceDiscoveryManager where entityID is the ID of the entity. The message +discoverInfo(entityID) will answer an instance of DiscoverInfo that contains +the discovered information.

+ +Examples

+ +In this example we can see how to check if the counterpart supports HOXT:
+

+
      // Obtain the ServiceDiscoveryManager associated with my XMPPConnection
+      ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+
+      // Get the information of a given XMPP entity
+      DiscoverInfo discoInfo = discoManager.discoverInfo("juliet@capulet.com");
+
+      // Check if room is HOXT is supported
+      discoInfo.containsFeature("urn:xmpp:http");
+
+
+ +
+ +
IQ exchange

+ +Description

+ +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).

+ +Usage

+ +

First you need to register a PacketListener to be able to handle intended IQs.

+For the HTTP client you: +

+ +For the HTTP server you: + + +

+ +Examples

+ +In this example we are HTTP client, so we send request (POST) and handle the response:
+

+
      // register listener for IQ packets
+      connection.addPacketListener(new IqPacketListener(), new PacketTypeFilter(IQ.class));
+
+      // create a request body
+      String urlEncodedMessage = "I_love_you";
+
+      // create request
+      HttpOverXmppReq.Req req = new HttpOverXmppReq.Req(HttpMethod.POST, "/mailbox");
+      req.setVersion("1.1");
+
+      // prepare headers
+      Set<Header> set = new HashSet<Header>();
+      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));
+
+      // provide body or request (not mandatory, - empty body is used for GET)
+      AbstractHttpOverXmpp.Text child = new AbstractHttpOverXmpp.Text(urlEncodedMessage);
+      AbstractHttpOverXmpp.Data data = new AbstractHttpOverXmpp.Data(child);
+      req.setData(data);
+
+      // create IQ packet
+      HttpOverXmppReq packet = new HttpOverXmppReq();
+      packet.setReq(req);
+      packet.setTo("juliet@capulet.com/balcony");
+      packet.setType(IQ.Type.SET);
+      packet.setPacketID("42");
+
+      // send it
+      connection.sendPacket(packet);
+
+
+      // then in your PacketListener
+      private class IqPacketListener implements PacketListener {
+
+            @Override
+            public void processPacket(Packet packet) {
+                  IQ iq = (IQ) packet;
+
+                  // verify from and packed ID
+                  if (iq.getFrom().equals("juliet@capulet.com/balcony") && (iq.getPacketID().equals("42"))) {
+
+                        // ensure it's not ERROR
+                        if (iq.getType().equals(IQ.Type.RESULT)) {
+
+                              // check if correct IQ implementation arrived
+                              if (iq instanceof HttpOverXmppResp) {
+                                    HttpOverXmppResp resp = (HttpOverXmppResp) iq;
+
+                                    // check HTTP response code
+                                    if (resp.getResp().getStatusCode() == 200) {
+
+                                          // get content of the response
+                                          AbstractHttpOverXmpp.DataChild child = resp.getResp().getData().getChild();
+
+                                          // check which type of content of the response arrived
+                                          if (child instanceof AbstractHttpOverXmpp.Xml) {
+
+                                                // print the message and anxiously read if from console ;)
+                                                System.out.println(((AbstractHttpOverXmpp.Xml) child).getText());
+                                          } else {
+                                                // process other AbstractHttpOverXmpp.DataChild subtypes
+                                          }
+                                    }
+                              }
+                        }
+                  }
+            }
+      }
+
+
+ +
+ + + + diff --git a/documentation/extensions/intro.html b/documentation/extensions/intro.html index 633d8e68f..8d1a41b0e 100644 --- a/documentation/extensions/intro.html +++ b/documentation/extensions/intro.html @@ -85,6 +85,11 @@ XEP-0016 Enabling or disabling communication with other entities. + + HTTP over XMPP transport + XEP-0332 + Allows to transport HTTP communication over XMPP peer-to-peer networks. + diff --git a/experimental/src/main/java/org/jivesoftware/smackx/ExperimentalStartupClasses.java b/experimental/src/main/java/org/jivesoftware/smackx/ExperimentalStartupClasses.java new file mode 100644 index 000000000..fc6073c2e --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/ExperimentalStartupClasses.java @@ -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 exceptions = new LinkedList(); + // 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 getExceptions() { + return Collections.unmodifiableList(exceptions); + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/HOXTManager.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/HOXTManager.java new file mode 100644 index 000000000..17c23f078 --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/HOXTManager.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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); + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java new file mode 100644 index 000000000..9c326b1c6 --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/AbstractHttpOverXmpp.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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.

+ * 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(""); + builder.append(child.toXML()); + builder.append(""); + 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.

+ * 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(""); + if (text != null) { + builder.append(text); + } + builder.append(""); + return builder.toString(); + } + + /** + * Returns text of this element. + * + * @return text + */ + public String getText() { + return text; + } + } + + /** + * Representation of Base64 element.

+ * 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(""); + if (text != null) { + builder.append(text); + } + builder.append(""); + return builder.toString(); + } + + /** + * Returns text of this element. + * + * @return text + */ + public String getText() { + return text; + } + } + + /** + * Representation of Xml element.

+ * 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(""); + if (text != null) { + builder.append(text); + } + builder.append(""); + return builder.toString(); + } + + /** + * Returns text of this element. + * + * @return text + */ + public String getText() { + return text; + } + } + + /** + * Representation of ChunkedBase64 element.

+ * 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(""); + return builder.toString(); + } + + /** + * Returns streamId attribute. + * + * @return streamId attribute + */ + public String getStreamId() { + return streamId; + } + } + + /** + * Representation of Ibb element.

+ * 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(""); + return builder.toString(); + } + + /** + * Returns sid attribute. + * + * @return sid attribute + */ + public String getSid() { + return sid; + } + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/Base64BinaryChunk.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/Base64BinaryChunk.java new file mode 100644 index 000000000..806a0b625 --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/Base64BinaryChunk.java @@ -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.

+ * This class is immutable. + * + * @author Andriy Tsykholyas + * @see XEP-0332: HTTP over XMPP transport + */ +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(""); + builder.append(text); + builder.append(""); + return builder.toString(); + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpMethod.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpMethod.java new file mode 100644 index 000000000..4b643c785 --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpMethod.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +public enum HttpMethod { + OPTIONS, + GET, + HEAD, + POST, + PUT, + DELETE, + TRACE, + PATCH +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppReq.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppReq.java new file mode 100644 index 000000000..c0b6b4104 --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppReq.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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(""); + return builder.toString(); + } + + @Override + protected String getEndTag() { + return ""; + } + + /** + * 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; + } + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppResp.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppResp.java new file mode 100644 index 000000000..3979a224b --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/packet/HttpOverXmppResp.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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(""); + return builder.toString(); + } + + @Override + protected String getEndTag() { + return ""; + } + + /** + * 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; + } + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java new file mode 100644 index 000000000..0cd9456f1 --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProvider.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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

set = new HashSet
(); + 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("'); + } 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; + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProvider.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProvider.java new file mode 100644 index 000000000..adfee7c12 --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProvider.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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); + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProvider.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProvider.java new file mode 100644 index 000000000..ee140711e --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProvider.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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; + } +} diff --git a/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProvider.java b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProvider.java new file mode 100644 index 000000000..57e63562d --- /dev/null +++ b/experimental/src/main/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProvider.java @@ -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 XEP-0332: HTTP over XMPP transport + */ +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; + } +} diff --git a/experimental/src/main/resources/org.jivesoftware.smackx/experimental.providers b/experimental/src/main/resources/org.jivesoftware.smackx/experimental.providers index e4ff30945..88b82db76 100644 --- a/experimental/src/main/resources/org.jivesoftware.smackx/experimental.providers +++ b/experimental/src/main/resources/org.jivesoftware.smackx/experimental.providers @@ -1,6 +1,23 @@ - + + + + + req + urn:xmpp:http + org.jivesoftware.smackx.hoxt.provider.HttpOverXmppReqProvider + + + resp + urn:xmpp:http + org.jivesoftware.smackx.hoxt.provider.HttpOverXmppRespProvider + + + chunk + urn:xmpp:http + org.jivesoftware.smackx.hoxt.provider.Base64BinaryChunkProvider + diff --git a/experimental/src/main/resources/org.jivesoftware.smackx/extensions.xml b/experimental/src/main/resources/org.jivesoftware.smackx/extensions.xml new file mode 100644 index 000000000..359f44a5b --- /dev/null +++ b/experimental/src/main/resources/org.jivesoftware.smackx/extensions.xml @@ -0,0 +1,5 @@ + + + org.jivesoftware.smackx.hoxt.HOXTManager + + \ No newline at end of file diff --git a/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProviderTest.java b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProviderTest.java new file mode 100644 index 000000000..bd71a106a --- /dev/null +++ b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/AbstractHttpOverXmppProviderTest.java @@ -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 = "" + + "" + + "
Fri, 03 May 2013 13:52:10 GMT-4
" + + "
OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE
" + + "
0
" + + "
" + + "
"; + Map expectedHeaders = new HashMap(); + 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 = "" + + "" + + "
clayster.com
" + + "
" + + "
"; + Map expectedHeaders = new HashMap(); + 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: ." + + "@base ." + + " dc:title \"HTTP over XMPP\";" + + "dc:creator ;" + + "dc:publisher ."; + String encodedText = "@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 string = "" + + "
Clayster
" + + "" + + encodedText + + "
"; + + AbstractHttpOverXmpp.Text text = (AbstractHttpOverXmpp.Text) parseAbstractBody( + string, "resp").getData().getChild(); + assertEquals(expectedText, text.getText()); + } + + @Test + public void isXmlDataParsedCorrectly() throws Exception { + String expectedXml = "" // no xmlns here + + "" + + "" + + "HTTP over XMPP" + + "" + + "" + + "http://clayster.com/PeterWaher" + + "" + + "" + + "" + + ""; + String encodedXml = "" + + "" + + "" + + "HTTP over XMPP" + + "" + + "" + + "http://clayster.com/PeterWaher" + + "" + + "" + + "" + + ""; + String string = "" + + "
Clayster
" + + "" + + encodedXml + + "
"; + 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 = "" + + "
Clayster
" + + "" + + base64Data + + "
"; + 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 = " "; + String string = "" + + "
Clayster
" + + "" + + chunkBase64Data + + "
"; + 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 = " "; + String string = "" + + "
Clayster
" + + "" + + ibbData + + "
"; + 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 expectedHeaders) { + Collection
collection = headers.getHeaders(); + + assertEquals(collection.size(), expectedHeaders.size()); + + for (Header header : collection) { + assertTrue(expectedHeaders.containsKey(header.getName())); + assertEquals(expectedHeaders.get(header.getName()), header.getValue()); + } + } +} diff --git a/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProviderTest.java b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProviderTest.java new file mode 100644 index 000000000..5c3e9c8b0 --- /dev/null +++ b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/Base64BinaryChunkProviderTest.java @@ -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 = "" + base64Text + ""; + + 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 = "" + base64Text + ""; + + 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()); + } +} diff --git a/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProviderTest.java b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProviderTest.java new file mode 100644 index 000000000..32bcaa391 --- /dev/null +++ b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppReqProviderTest.java @@ -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 = ""; + 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 = ""; + 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 = ""; + 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 = ""; + 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(); + } +} diff --git a/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProviderTest.java b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProviderTest.java new file mode 100644 index 000000000..da0be2e90 --- /dev/null +++ b/experimental/src/test/java/org/jivesoftware/smackx/hoxt/provider/HttpOverXmppRespProviderTest.java @@ -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 = ""; + 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 = ""; + 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()); + } +}