diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md
index 481229840..325fcf30a 100644
--- a/documentation/extensions/index.md
+++ b/documentation/extensions/index.md
@@ -91,6 +91,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. |
| Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. |
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
+| HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. |
| Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers |
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java
new file mode 100644
index 000000000..dfcc1f790
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java
@@ -0,0 +1,375 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload;
+
+import org.jivesoftware.smack.AbstractConnectionListener;
+import org.jivesoftware.smack.ConnectionCreationListener;
+import org.jivesoftware.smack.Manager;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPConnectionRegistry;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
+import org.jivesoftware.smackx.httpfileupload.element.Slot;
+import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
+import org.jivesoftware.smackx.xdata.FormField;
+import org.jivesoftware.smackx.xdata.packet.DataForm;
+import org.jxmpp.jid.DomainBareJid;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * HTTP File Upload manager class.
+ * XEP version 0.2.5
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+public final class HttpFileUploadManager extends Manager {
+
+ private static final Logger LOGGER = Logger.getLogger(HttpFileUploadManager.class.getName());
+
+ static {
+ XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
+ @Override
+ public void connectionCreated(XMPPConnection connection) {
+ getInstanceFor(connection);
+ }
+ });
+ }
+
+ private static final Map INSTANCES = new WeakHashMap<>();
+ private DomainBareJid defaultUploadService;
+ private Long maxFileSize;
+
+ /**
+ * Obtain the HttpFileUploadManager responsible for a connection.
+ *
+ * @param connection the connection object.
+ * @return a HttpFileUploadManager instance
+ */
+ public static synchronized HttpFileUploadManager getInstanceFor(XMPPConnection connection) {
+ HttpFileUploadManager httpFileUploadManager = INSTANCES.get(connection);
+
+ if (httpFileUploadManager == null) {
+ httpFileUploadManager = new HttpFileUploadManager(connection);
+ INSTANCES.put(connection, httpFileUploadManager);
+ }
+
+ return httpFileUploadManager;
+ }
+
+ private HttpFileUploadManager(XMPPConnection connection) {
+ super(connection);
+
+ connection.addConnectionListener(new AbstractConnectionListener() {
+ @Override
+ public void authenticated(XMPPConnection connection, boolean resumed) {
+ // No need to reset the cache if the connection got resumed.
+ if (resumed) {
+ return;
+ }
+
+ try {
+ discoverUploadService();
+ } catch (XMPPException.XMPPErrorException | SmackException.NotConnectedException
+ | SmackException.NoResponseException | InterruptedException e) {
+ LOGGER.log(Level.WARNING, "Error during discovering HTTP File Upload service", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Discover upload service.
+ *
+ * Called automatically when connection is authenticated.
+ *
+ * Note that this is a synchronous call -- Smack must wait for the server response.
+ *
+ * @return true if upload service was discovered
+
+ * @throws XMPPException.XMPPErrorException
+ * @throws SmackException.NotConnectedException
+ * @throws InterruptedException
+ * @throws SmackException.NoResponseException
+ */
+ public boolean discoverUploadService() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
+ InterruptedException, SmackException.NoResponseException {
+ defaultUploadService = null;
+ maxFileSize = null;
+
+ List servicesDiscoverInfo = ServiceDiscoveryManager.getInstanceFor(connection())
+ .findServicesDiscoverInfo(SlotRequest.NAMESPACE, true, false);
+
+ if (servicesDiscoverInfo.isEmpty()) {
+ return false;
+ }
+
+ DiscoverInfo discoverInfo = servicesDiscoverInfo.get(0);
+
+ defaultUploadService = discoverInfo.getFrom().asDomainBareJid();
+
+ if (defaultUploadService == null) {
+ return false;
+ }
+
+ DataForm dataForm = DataForm.from(discoverInfo);
+ if (dataForm == null) {
+ return true;
+ }
+
+ FormField field = dataForm.getField("max-file-size");
+ if (field == null) {
+ return true;
+ }
+
+ List values = field.getValues();
+ if (!values.isEmpty()) {
+ maxFileSize = Long.valueOf(values.get(0));
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if upload service was discovered.
+ *
+ * @return true if upload service was discovered
+ */
+ public boolean isUploadServiceDiscovered() {
+ return defaultUploadService != null;
+ }
+
+ /**
+ * Get default upload service if it was discovered.
+ *
+ * @return upload service JID or null if not available
+ */
+ public DomainBareJid getDefaultUploadService() {
+ return defaultUploadService;
+ }
+
+ /**
+ * Get max file size allowed by upload service.
+ *
+ * @return max file size in bytes or null if not available
+ */
+ public Long getMaxFileSize() {
+ return maxFileSize;
+ }
+
+ /**
+ * Request slot and uploaded file to HTTP file upload service.
+ *
+ * You don't need to request slot and upload file separately, this method will do both.
+ * Note that this is a synchronous call -- Smack must wait for the server response.
+ *
+ * @param file file to be uploaded
+ * @return public URL for sharing uploaded file
+ * @throws InterruptedException
+ * @throws XMPPException.XMPPErrorException
+ * @throws SmackException
+ * @throws IOException in case of HTTP upload errors
+ */
+ public URL uploadFile(File file) throws InterruptedException, XMPPException.XMPPErrorException,
+ SmackException, IOException {
+ return uploadFile(file, null);
+ }
+
+ /**
+ * Callback interface to get upload progress.
+ */
+ public interface UploadProgressListener {
+ /**
+ * Callback for displaying upload progress.
+ *
+ * @param uploadedBytes - number of bytes uploaded at the moment
+ * @param totalBytes - total number of bytes to be uploaded
+ */
+ void onUploadProgress(long uploadedBytes, long totalBytes);
+ }
+
+ /**
+ * Request slot and uploaded file to HTTP file upload service with progress callback.
+ *
+ * You don't need to request slot and upload file separately, this method will do both.
+ * Note that this is a synchronous call -- Smack must wait for the server response.
+ *
+ * @param file file to be uploaded
+ * @param listener upload progress listener of null
+ * @return public URL for sharing uploaded file
+ *
+ * @throws InterruptedException
+ * @throws XMPPException.XMPPErrorException
+ * @throws SmackException
+ * @throws IOException
+ */
+ public URL uploadFile(File file, UploadProgressListener listener) throws InterruptedException,
+ XMPPException.XMPPErrorException, SmackException, IOException {
+ final Slot slot = requestSlot(file.getName(), file.length(), "application/octet-stream");
+
+ uploadFile(file, slot.getPutUrl(), listener);
+
+ return slot.getGetUrl();
+ }
+
+
+ /**
+ * Request a new upload slot from default upload service (if discovered).
+ *
+ * When you get slot you should upload file to PUT URL and share GET URL.
+ * Note that this is a synchronous call -- Smack must wait for the server response.
+ *
+ * @param filename name of file to be uploaded
+ * @param fileSize file size in bytes -- must be less or equal
+ * to {@link HttpFileUploadManager#getMaxFileSize()} (if available)
+ * @return file upload Slot in case of success
+
+ * @throws IllegalArgumentException if fileSize is less than or equal to zero
+ * or greater than {@link HttpFileUploadManager#getMaxFileSize()}
+ * @throws InterruptedException
+ * @throws XMPPException.XMPPErrorException
+ * @throws SmackException.NotConnectedException
+ * @throws SmackException.NoResponseException
+ */
+ public Slot requestSlot(String filename, long fileSize) throws InterruptedException,
+ XMPPException.XMPPErrorException, SmackException {
+ return requestSlot(filename, fileSize, null, null);
+ }
+
+ /**
+ * Request a new upload slot with optional content type from default upload service (if discovered).
+ *
+ * When you get slot you should upload file to PUT URL and share GET URL.
+ * Note that this is a synchronous call -- Smack must wait for the server response.
+ *
+ * @param filename name of file to be uploaded
+ * @param fileSize file size in bytes -- must be less or equal
+ * to {@link HttpFileUploadManager#getMaxFileSize()} (if available)
+ * @param contentType file content-type or null
+ * @return file upload Slot in case of success
+
+ * @throws IllegalArgumentException if fileSize is less than or equal to zero
+ * or greater than {@link HttpFileUploadManager#getMaxFileSize()}
+ * @throws SmackException.NotConnectedException
+ * @throws InterruptedException
+ * @throws XMPPException.XMPPErrorException
+ * @throws SmackException.NoResponseException
+ */
+ public Slot requestSlot(String filename, long fileSize, String contentType) throws SmackException,
+ InterruptedException, XMPPException.XMPPErrorException {
+ return requestSlot(filename, fileSize, contentType, null);
+ }
+
+ /**
+ * Request a new upload slot with optional content type from custom upload service.
+ *
+ * When you get slot you should upload file to PUT URL and share GET URL.
+ * Note that this is a synchronous call -- Smack must wait for the server response.
+ *
+ * @param filename name of file to be uploaded
+ * @param fileSize file size in bytes -- must be less or equal
+ * to {@link HttpFileUploadManager#getMaxFileSize()} (if available)
+ * @param contentType file content-type or null
+ * @param uploadService upload service to use or null for default one
+ * @return file upload Slot in case of success
+ * @throws IllegalArgumentException if fileSize is less than or equal to zero
+ * or greater than {@link HttpFileUploadManager#getMaxFileSize()}
+ * @throws SmackException
+ * @throws InterruptedException
+ * @throws XMPPException.XMPPErrorException
+ */
+ public Slot requestSlot(String filename, long fileSize, String contentType, DomainBareJid uploadService)
+ throws SmackException, InterruptedException, XMPPException.XMPPErrorException {
+ if (defaultUploadService == null && uploadService == null) {
+ throw new SmackException("No upload service specified or discovered.");
+ }
+
+ if (uploadService == null && maxFileSize != null) {
+ if (fileSize > maxFileSize) {
+ throw new IllegalArgumentException("Requested file size " + fileSize
+ + " is greater than max allowed size " + maxFileSize);
+ }
+ }
+
+ SlotRequest slotRequest = new SlotRequest(filename, fileSize, contentType);
+ if (uploadService != null) {
+ slotRequest.setTo(uploadService);
+ } else {
+ slotRequest.setTo(defaultUploadService);
+ }
+
+ return connection().createStanzaCollectorAndSend(slotRequest).nextResultOrThrow();
+ }
+
+ private void uploadFile(File file, URL putUrl, UploadProgressListener listener) throws IOException {
+ final HttpURLConnection urlConnection = (HttpURLConnection) putUrl.openConnection();
+ urlConnection.setRequestMethod("PUT");
+ urlConnection.setUseCaches(false);
+ urlConnection.setDoOutput(true);
+ urlConnection.setRequestProperty("Content-Type", "application/octet-stream;");
+ OutputStream outputStream = urlConnection.getOutputStream();
+
+ long bytesSend = 0;
+
+ long fileSize = file.length();
+ if (listener != null) {
+ listener.onUploadProgress(0, fileSize);
+ }
+
+ BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytesRead);
+ outputStream.flush();
+ bytesSend += bytesRead;
+
+ if (listener != null) {
+ listener.onUploadProgress(bytesSend, fileSize);
+ }
+
+ }
+
+ inputStream.close();
+ outputStream.close();
+
+ int status = urlConnection.getResponseCode();
+ if (status != HttpURLConnection.HTTP_CREATED) {
+ throw new IOException("Error response from server during file upload:"
+ + " " + urlConnection.getResponseCode()
+ + " " + urlConnection.getResponseMessage()
+ + ", file size: " + fileSize
+ + ", put URL: " + putUrl);
+ }
+ }
+
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/FileTooLargeError.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/FileTooLargeError.java
new file mode 100644
index 000000000..57eec5d62
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/FileTooLargeError.java
@@ -0,0 +1,70 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload.element;
+
+import org.jivesoftware.smack.packet.ExtensionElement;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smack.util.XmlStringBuilder;
+
+/**
+ * File Too Large error extension.
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+public class FileTooLargeError implements ExtensionElement {
+ public static final String ELEMENT = "file-too-large";
+ public static final String NAMESPACE = SlotRequest.NAMESPACE;
+
+ private final long maxFileSize;
+
+ public FileTooLargeError(long maxFileSize) {
+ this.maxFileSize = maxFileSize;
+ }
+
+ public long getMaxFileSize() {
+ return maxFileSize;
+ }
+
+ @Override
+ public String getElementName() {
+ return ELEMENT;
+ }
+
+ @Override
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ @Override
+ public CharSequence toXML() {
+ XmlStringBuilder xml = new XmlStringBuilder(this);
+ xml.rightAngleBracket();
+ xml.element("max-file-size", String.valueOf(maxFileSize));
+ xml.closeElement(ELEMENT);
+ return xml;
+ }
+
+ public static FileTooLargeError from(IQ iq) {
+ XMPPError error = iq.getError();
+ if (error == null) {
+ return null;
+ }
+ return error.getExtension(ELEMENT, NAMESPACE);
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/Slot.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/Slot.java
new file mode 100644
index 000000000..470987dc4
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/Slot.java
@@ -0,0 +1,62 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload.element;
+
+import org.jivesoftware.smack.packet.IQ;
+
+import java.net.URL;
+
+/**
+ * Slot responded by upload service.
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+public class Slot extends IQ {
+
+ public static final String ELEMENT = "slot";
+ public static final String NAMESPACE = SlotRequest.NAMESPACE;
+
+ private final URL putUrl;
+ private final URL getUrl;
+
+ public Slot(URL putUrl, URL getUrl) {
+ super(ELEMENT, NAMESPACE);
+ setType(Type.result);
+ this.putUrl = putUrl;
+ this.getUrl = getUrl;
+ }
+
+ public URL getPutUrl() {
+ return putUrl;
+ }
+
+ public URL getGetUrl() {
+ return getUrl;
+ }
+
+
+ @Override
+ protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
+ xml.rightAngleBracket();
+
+ xml.element("put", putUrl.toString());
+ xml.element("get", getUrl.toString());
+
+ return xml;
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest.java
new file mode 100644
index 000000000..bcdb823e6
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/SlotRequest.java
@@ -0,0 +1,84 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload.element;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * Upload slot request.
+
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+public class SlotRequest extends IQ {
+ public static final String ELEMENT = "request";
+ public static final String NAMESPACE = "urn:xmpp:http:upload";
+
+ private final String filename;
+ private final long size;
+ private final String contentType;
+
+
+ /**
+ * Create new slot request.
+ *
+ * @throws IllegalArgumentException if size is less than or equal to zero
+ * @param filename name of file
+ * @param size size of file in bytes
+ * @param contentType file content type or null
+ */
+ public SlotRequest(String filename, long size, String contentType) {
+ super(ELEMENT, NAMESPACE);
+
+ if (size <= 0) {
+ throw new IllegalArgumentException("File fileSize must be greater than zero.");
+ }
+
+ this.filename = filename;
+ this.size = size;
+ this.contentType = contentType;
+
+ setType(Type.get);
+ }
+
+ public SlotRequest(String filename, long size) {
+ this(filename, size, null);
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
+ xml.rightAngleBracket();
+ xml.element("filename", filename);
+ xml.element("size", String.valueOf(size));
+ if (contentType != null) {
+ xml.element("content-type", contentType);
+ }
+ return xml;
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/package-info.java
new file mode 100644
index 000000000..d20c3658b
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/element/package-info.java
@@ -0,0 +1,24 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.
+ */
+
+/**
+ * IQ stanzas and extensions for XEP-0363: HTTP File Upload.
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+package org.jivesoftware.smackx.httpfileupload.element;
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/package-info.java
new file mode 100644
index 000000000..d61996ae9
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/package-info.java
@@ -0,0 +1,24 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.
+ */
+
+/**
+ * Smack's API for XEP-0363: HTTP File Upload.
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+package org.jivesoftware.smackx.httpfileupload;
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/FileTooLargeErrorProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/FileTooLargeErrorProvider.java
new file mode 100644
index 000000000..3f1accb7c
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/FileTooLargeErrorProvider.java
@@ -0,0 +1,57 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload.provider;
+
+import org.jivesoftware.smack.provider.ExtensionElementProvider;
+import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Provider for File Too Large error extension.
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+public class FileTooLargeErrorProvider extends ExtensionElementProvider {
+
+ @Override
+ public FileTooLargeError parse(XmlPullParser parser, int initialDepth) throws Exception {
+ Long maxFileSize = null;
+
+ outerloop: while(true) {
+ int event = parser.next();
+
+ switch (event) {
+ case XmlPullParser.START_TAG:
+ String name = parser.getName();
+ switch(name) {
+ case "max-file-size":
+ maxFileSize = Long.valueOf(parser.nextText());
+ break;
+ }
+ break;
+ case XmlPullParser.END_TAG:
+ if (parser.getDepth() == initialDepth) {
+ break outerloop;
+ }
+ break;
+ }
+ }
+
+ return new FileTooLargeError(maxFileSize);
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/SlotProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/SlotProvider.java
new file mode 100644
index 000000000..a30eb9330
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/SlotProvider.java
@@ -0,0 +1,66 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload.provider;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smackx.httpfileupload.element.Slot;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Provider for Slot.
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+public class SlotProvider extends IQProvider {
+
+ @Override
+ public Slot parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException {
+ URL putUrl = null;
+ URL getUrl = null;
+
+ outerloop: while (true) {
+ int event = parser.next();
+
+ switch (event) {
+ case XmlPullParser.START_TAG:
+ String name = parser.getName();
+ switch(name) {
+ case "put":
+ putUrl = new URL(parser.nextText());
+ break;
+ case "get":
+ getUrl = new URL(parser.nextText());
+ break;
+ }
+ break;
+ case XmlPullParser.END_TAG:
+ if (parser.getDepth() == initialDepth) {
+ break outerloop;
+ }
+ break;
+ }
+ }
+
+ return new Slot(putUrl, getUrl);
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/package-info.java
new file mode 100644
index 000000000..3894df689
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/provider/package-info.java
@@ -0,0 +1,24 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.
+ */
+
+/**
+ * Providers for XEP-0363: HTTP File Upload.
+ *
+ * @author Grigory Fedorov
+ * @see XEP-0363: HTTP File Upload
+ */
+package org.jivesoftware.smackx.httpfileupload.provider;
diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers
index 55b89911d..0b9f7cf8a 100644
--- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers
+++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers
@@ -232,5 +232,17 @@
urn:xmpp:chat-markers:0
org.jivesoftware.smackx.chat_markers.provider.AcknowledgedProvider
-
+
+
+
+ slot
+ urn:xmpp:http:upload
+ org.jivesoftware.smackx.httpfileupload.provider.SlotProvider
+
+
+ file-too-large
+ urn:xmpp:http:upload
+ org.jivesoftware.smackx.httpfileupload.provider.FileTooLargeErrorProvider
+
+
diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml
index c621a0d43..05b819a27 100644
--- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml
+++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml
@@ -4,5 +4,6 @@
org.jivesoftware.smackx.iot.data.IoTDataManager
org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager
org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager
+ org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/FileTooLargeErrorCreateTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/FileTooLargeErrorCreateTest.java
new file mode 100644
index 000000000..40c79f335
--- /dev/null
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/FileTooLargeErrorCreateTest.java
@@ -0,0 +1,39 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload;
+
+
+import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FileTooLargeErrorCreateTest {
+ String fileTooLargeErrorExtensionExample
+ = ""
+ + "20000"
+ + "";
+
+ @Test
+ public void checkFileTooLargeErrorExtensionCreation() {
+ FileTooLargeError fileTooLargeError = new FileTooLargeError(20000);
+
+ Assert.assertEquals(20000, fileTooLargeError.getMaxFileSize());
+ Assert.assertEquals(fileTooLargeErrorExtensionExample, fileTooLargeError.toXML().toString());
+
+ }
+
+}
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/FileTooLargeErrorProviderTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/FileTooLargeErrorProviderTest.java
new file mode 100644
index 000000000..10573d938
--- /dev/null
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/FileTooLargeErrorProviderTest.java
@@ -0,0 +1,58 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FileTooLargeErrorProviderTest {
+
+ /**
+ * Example 7. Alternative response by the upload service if the file size was too large
+ * @see XEP-0363: HTTP File Upload 5. Error conditions
+ */
+ String slotErrorFileToLarge
+ = ""
+ + ""
+ + "my_juliet.png"
+ + "23456"
+ + ""
+ + ""
+ + ""
+ + "File too large. The maximum file size is 20000 bytes"
+ + ""
+ + "20000"
+ + ""
+ + ""
+ + "";
+
+ @Test
+ public void checkSlotErrorFileToLarge() throws Exception {
+ IQ fileTooLargeErrorIQ = PacketParserUtils.parseStanza(slotErrorFileToLarge);
+
+ Assert.assertEquals(IQ.Type.error, fileTooLargeErrorIQ.getType());
+
+ FileTooLargeError fileTooLargeError = FileTooLargeError.from(fileTooLargeErrorIQ);
+ Assert.assertEquals(20000, fileTooLargeError.getMaxFileSize());
+ }
+}
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotCreateTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotCreateTest.java
new file mode 100644
index 000000000..838de9466
--- /dev/null
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotCreateTest.java
@@ -0,0 +1,46 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload;
+
+
+import org.jivesoftware.smackx.httpfileupload.element.Slot;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class SlotCreateTest {
+ String testSlot
+ = ""
+ + "https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"
+ + "https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"
+ + "";
+
+ @Test
+ public void checkSlotRequestCreation() throws MalformedURLException {
+ Slot slot = new Slot(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
+ new URL("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"));
+
+ Assert.assertEquals(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
+ slot.getPutUrl());
+ Assert.assertEquals(new URL("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
+ slot.getGetUrl());
+
+ Assert.assertEquals(testSlot, slot.getChildElementXML().toString());
+ }
+}
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotProviderTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotProviderTest.java
new file mode 100644
index 000000000..13b830b67
--- /dev/null
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotProviderTest.java
@@ -0,0 +1,58 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.jivesoftware.smackx.httpfileupload.element.Slot;
+import org.jivesoftware.smackx.httpfileupload.provider.SlotProvider;
+import org.junit.Assert;
+import org.junit.Test;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.net.URL;
+
+
+public class SlotProviderTest {
+
+ /**
+ * Example 6. The upload service responds with a slot
+ * @see XEP-0363: HTTP File Upload 4. Requesting a slot
+ */
+ String slotExample
+ = ""
+ + ""
+ + "https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"
+ + "https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"
+ + ""
+ + "";
+
+ @Test
+ public void checkSlotProvider() throws Exception {
+ XmlPullParser parser = PacketParserUtils.getParserFor(slotExample);
+ Slot slot = new SlotProvider().parse(parser);
+
+ Assert.assertEquals(IQ.Type.result, slot.getType());
+ Assert.assertEquals(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
+ slot.getPutUrl());
+ Assert.assertEquals(new URL("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
+ slot.getGetUrl());
+ }
+}
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java
new file mode 100644
index 000000000..e7ef98bb1
--- /dev/null
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java
@@ -0,0 +1,71 @@
+/**
+ *
+ * Copyright © 2017 Grigory Fedorov
+ *
+ * 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.httpfileupload;
+
+import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.jxmpp.stringprep.XmppStringprepException;
+
+
+public class SlotRequestCreateTest {
+
+ String testRequest
+ = ""
+ + "my_juliet.png"
+ + "23456"
+ + "image/jpeg"
+ + "";
+
+ String testRequestWithoutContentType
+ = ""
+ + "my_romeo.png"
+ + "52523"
+ + "";
+
+ @Test
+ public void checkSlotRequestCreation() throws XmppStringprepException {
+ SlotRequest slotRequest = new SlotRequest("my_juliet.png", 23456, "image/jpeg");
+
+ Assert.assertEquals("my_juliet.png", slotRequest.getFilename());
+ Assert.assertEquals(23456, slotRequest.getSize());
+ Assert.assertEquals("image/jpeg", slotRequest.getContentType());
+
+ Assert.assertEquals(testRequest, slotRequest.getChildElementXML().toString());
+ }
+
+ @Test
+ public void checkSlotRequestCreationWithoutContentType() throws XmppStringprepException {
+ SlotRequest slotRequest = new SlotRequest("my_romeo.png", 52523);
+
+ Assert.assertEquals("my_romeo.png", slotRequest.getFilename());
+ Assert.assertEquals(52523, slotRequest.getSize());
+ Assert.assertEquals(null, slotRequest.getContentType());
+
+ Assert.assertEquals(testRequestWithoutContentType, slotRequest.getChildElementXML().toString());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void checkSlotRequestCreationNegativeSize() {
+ new SlotRequest("my_juliet.png", -23456, "image/jpeg");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void checkSlotRequestCreationZeroSize() {
+ new SlotRequest("my_juliet.png", 0, "image/jpeg");
+ }
+}