aboutsummaryrefslogtreecommitdiffstats
path: root/src/main
diff options
context:
space:
mode:
authorsteckbrief <steckbrief@chefmail.de>2016-08-22 21:30:36 +0200
committersteckbrief <steckbrief@chefmail.de>2016-08-22 21:30:36 +0200
commit3ed7cb54e5858afaadc3f7ec5bc01edb61e1428e (patch)
tree56013b55e801ec8f5de5aaea39fcdb203c40b15c /src/main
parent77d0c1110a8ae7a980f1c4420d719af803990da8 (diff)
Basic filetransfer http delete implementation; Exceptions for IqPacketError added
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/Message.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java37
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java81
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java22
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java30
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java83
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java11
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java10
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketParser.java (renamed from src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractIqPacketParser.java)32
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/BadRequestIqErrorException.java17
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/InternalServerErrorException.java17
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/IqPacketErrorException.java20
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/ServiceUnavailableException.java17
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/UndefinedConditionException.java17
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotPacketParser.java28
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotRequestPacket.java33
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttp.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttpDeleteSlotRequestPacketGenerator.java39
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java44
-rw-r--r--src/main/res/menu/message_context.xml4
-rw-r--r--src/main/res/values/strings.xml1
22 files changed, 547 insertions, 15 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
index 26854205..8f970a09 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
@@ -10,6 +10,7 @@ import java.io.File;
import java.security.SecureRandom;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.thedevstack.conversationsplus.http.HttpClient;
import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
@@ -18,6 +19,7 @@ import de.thedevstack.conversationsplus.services.filetransfer.jingle.JingleFileT
import de.thedevstack.conversationsplus.utils.ImageUtil;
import de.thedevstack.conversationsplus.utils.PRNGFixes;
import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor;
+import okhttp3.OkHttpClient;
/**
* This class is used to provide static access to the applicationcontext.
@@ -46,6 +48,7 @@ public class ConversationsPlusApplication extends Application {
FileBackend.init();
FileTransferManager.init(new HttpUploadFileTransferService(), new JingleFileTransferService());
HttpConnectionManager.init();
+ HttpClient.init();
}
/**
@@ -147,6 +150,11 @@ public class ConversationsPlusApplication extends Application {
getInstance().setMemorizingTrustManager(tm);
}
+ private static void initHttpClient() {
+ OkHttpClient client = new OkHttpClient.Builder()
+ .build();
+ }
+
public static SecureRandom getSecureRandom() {
return getInstance().secureRandom;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java
index b0a5bb3b..f081e82a 100644
--- a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java
+++ b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java
@@ -27,6 +27,9 @@ public class Message extends AbstractEntity {
public static final int STATUS_SEND_RECEIVED = 7;
public static final int STATUS_SEND_DISPLAYED = 8;
+ public static final int STATUS_REMOTE_FILE_DELETE_FAILED = 101;
+ public static final int STATUS_REMOTE_FILE_DELETED = 102;
+
public static final int ENCRYPTION_NONE = 0;
public static final int ENCRYPTION_PGP = 1;
public static final int ENCRYPTION_OTR = 2;
diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java
new file mode 100644
index 00000000..76c1dbc9
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java
@@ -0,0 +1,37 @@
+package de.thedevstack.conversationsplus.entities;
+
+import android.support.annotation.NonNull;
+
+import java.io.Serializable;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public class RemoteFile implements Serializable {
+ private static final long serialVersionUID = 34564871234564L;
+ private final String path;
+
+ public RemoteFile(@NonNull String path) {
+ this.path = path;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RemoteFile that = (RemoteFile) o;
+
+ return path.equals(that.path);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return path.hashCode();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java
new file mode 100644
index 00000000..7e12a890
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java
@@ -0,0 +1,81 @@
+package de.thedevstack.conversationsplus.http;
+
+import org.apache.http.conn.ssl.StrictHostnameVerifier;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.utils.CryptoHelper;
+import de.thedevstack.conversationsplus.utils.SSLSocketHelper;
+import okhttp3.OkHttpClient;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public final class HttpClient {
+ private static HttpClient INSTANCE;
+ private boolean interactive = false;
+ private OkHttpClient client;
+
+ public static void init() {
+ INSTANCE = new HttpClient();
+ }
+
+ public static synchronized OkHttpClient getClient(boolean interactive) {
+ if (INSTANCE.interactive != interactive) {
+ INSTANCE.interactive = interactive;
+ INSTANCE.buildClient();
+ }
+ return INSTANCE.client;
+ }
+
+ private HttpClient() {
+ this.buildClient();
+ }
+
+ private void buildClient() {
+ OkHttpClient.Builder builder = new OkHttpClient.Builder();
+ this.initTrustManager(builder);
+ this.client = builder.build();
+ }
+
+ public void initTrustManager(final OkHttpClient.Builder builder) {
+ final X509TrustManager trustManager;
+ final HostnameVerifier hostnameVerifier;
+ if (interactive) {
+ trustManager = ConversationsPlusApplication.getMemorizingTrustManager();
+ hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager().wrapHostnameVerifier(
+ new StrictHostnameVerifier());
+ } else {
+ trustManager = ConversationsPlusApplication.getMemorizingTrustManager()
+ .getNonInteractive();
+ hostnameVerifier = ConversationsPlusApplication.getMemorizingTrustManager()
+ .wrapHostnameVerifierNonInteractive(
+ new StrictHostnameVerifier());
+ }
+ try {
+ final SSLContext sc = SSLSocketHelper.getSSLContext();
+ sc.init(null, new X509TrustManager[]{trustManager},
+ ConversationsPlusApplication.getSecureRandom());
+
+ final SSLSocketFactory sf = sc.getSocketFactory();
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
+ sf.getSupportedCipherSuites());
+ if (cipherSuites.length > 0) {
+ sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
+
+ }
+
+ builder.sslSocketFactory(sf, trustManager);
+ builder.hostnameVerifier(hostnameVerifier);
+ } catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java
new file mode 100644
index 00000000..9f6b3bbd
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java
@@ -0,0 +1,22 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http;
+
+import android.support.annotation.NonNull;
+
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.entities.RemoteFile;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public class DeleteRemoteFile extends RemoteFile {
+ private final Message message;
+
+ public DeleteRemoteFile(@NonNull String path, @NonNull Message message) {
+ super(path);
+ this.message = message;
+ }
+
+ public Message getMessage() {
+ return message;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java
new file mode 100644
index 00000000..57f408cc
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java
@@ -0,0 +1,30 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http;
+
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttp;
+import de.thedevstack.conversationsplus.xmpp.filetransfer.http.FileTransferHttpDeleteSlotRequestPacketGenerator;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
+
+/**
+ * Created by steckbrief on 21.08.2016.
+ */
+public class DeleteRemoteFileService {
+ public void deleteRemoteFile(Message message) {
+ if (message.isHttpUploaded()) {
+ String path = message.getBody();
+ if (message.hasFileOnRemoteHost()) {
+ path = message.getFileParams().url.toString();
+ }
+
+ DeleteRemoteFile remoteFile = new DeleteRemoteFile(path, message);
+ Account account = message.getConversation().getAccount();
+ Jid host = account.getXmppConnection().findDiscoItemByFeature(FileTransferHttp.NAMESPACE);
+ IqPacket request = FileTransferHttpDeleteSlotRequestPacketGenerator.generate(host, path);
+
+ XmppSendUtil.sendIqPacket(account, request, new DeleteTokenReceived(remoteFile));
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java
new file mode 100644
index 00000000..fb83219b
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java
@@ -0,0 +1,83 @@
+package de.thedevstack.conversationsplus.services.filetransfer.http;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.http.HttpClient;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived;
+import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException;
+import de.thedevstack.conversationsplus.xmpp.filetransfer.http.DeleteSlotPacketParser;
+import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * Created by steckbrief on 21.08.2016.
+ */
+public class DeleteTokenReceived implements OnIqPacketReceived {
+ private static final String HEADER_NAME_DELETE_TOKEN = "X-FILETRANSFER-HTTP-DELETE-TOKEN";
+ private final DeleteRemoteFile remoteFile;
+
+ public DeleteTokenReceived(DeleteRemoteFile remoteFile) {
+ this.remoteFile = remoteFile;
+ }
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ try {
+ String url = this.remoteFile.getPath();
+ String deleteToken = DeleteSlotPacketParser.parseDeleteToken(packet);
+ Logging.d("filetransfer.http.delete", "Got delete token '" + deleteToken + "' for remote file '" + remoteFile.getPath() + "'");
+ OkHttpClient client = HttpClient.getClient(true);
+ Request request = new Request.Builder()
+ .url(url)
+ .addHeader(HEADER_NAME_DELETE_TOKEN, deleteToken)
+ .delete()
+ .build();
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ Logging.e("filetransfer.http.delete", "Error while connecting to '" + call.request().url() + "': " + e.getMessage());
+ MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED);
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ if (response.isSuccessful()) {
+ MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETED);
+ Logging.i("filetransfer.http.delete", "Remote file successfully deleted '" + remoteFile.getPath() + "'");
+ } else {
+ String detailedMessage = response.body().string();
+ switch (response.code()) {
+ case 403:
+ case 404:
+ case 500:
+ try {
+ JSONObject jsonObject = new JSONObject(detailedMessage);
+ detailedMessage = jsonObject.getString("msg");
+ } catch (JSONException e) {
+ Logging.e("filetransfer.http.delete", "Failed to get error message from expected json response: " + detailedMessage);
+ }
+ break;
+ }
+ Logging.e("filetransfer.http.delete", "Could not delete remote file '" + remoteFile.getPath() + "'. Response Code: " + response.code() + ", details: " + detailedMessage);
+ MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED);
+ }
+ }
+ });
+
+ } catch (XmppException e) {
+ Logging.e("filetransfer.http.delete", "Error while trying to get the delete token: " + e.getMessage());
+ MessageUtil.markMessage(remoteFile.getMessage(), Message.STATUS_REMOTE_FILE_DELETE_FAILED);
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
index d6e6f34c..934f16b6 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
@@ -8,7 +8,6 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -24,8 +23,6 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ImageButton;
@@ -48,6 +45,7 @@ import java.util.List;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import de.thedevstack.conversationsplus.http.HttpConnectionManager;
import de.thedevstack.conversationsplus.http.HttpDownloadConnection;
+import de.thedevstack.conversationsplus.services.filetransfer.http.DeleteRemoteFileService;
import de.thedevstack.conversationsplus.ui.dialogs.MessageDetailsDialog;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.R;
@@ -576,12 +574,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
deleteFile.setVisible(true);
deleteFile.setTitle(activity.getString(R.string.delete_x_file,UIHelper.getFileDescriptionString(activity, m)));
}
+ if (m.isHttpUploaded() && (MessageUtil.isMessageSent(m) || m.getStatus() == Message.STATUS_REMOTE_FILE_DELETE_FAILED)) {
+ MenuItem deleteRemoteFile = menu.findItem(R.id.msg_ctx_menu_delete_remote_file);
+ deleteRemoteFile.setVisible(true);
+ }
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case R.id.msg_ctx_menu_delete_remote_file:
+ new DeleteRemoteFileService().deleteRemoteFile(selectedMessage);
+ return true;
case R.id.msg_ctx_mnu_details:
new MessageDetailsDialog(getActivity(), selectedMessage).show();
return true;
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
index 67433b31..626b1bf3 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
@@ -18,6 +18,16 @@ import de.thedevstack.conversationsplus.persistance.FileBackend;
*/
public final class MessageUtil {
+ public static boolean isMessageSent(Message message) {
+ switch (message.getStatus()) {
+ case Message.STATUS_SEND:
+ case Message.STATUS_SEND_DISPLAYED:
+ case Message.STATUS_SEND_RECEIVED:
+ return true;
+ default:
+ return false;
+ }
+ }
public static boolean markMessage(Conversation conversation, String uuid, int status) {
if (uuid == null) {
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractIqPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketParser.java
index d777cb64..7c198ce9 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractIqPacketParser.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketParser.java
@@ -7,19 +7,23 @@ import de.thedevstack.conversationsplus.xmpp.exceptions.MissingRequiredElementEx
/**
*
*/
-public abstract class AbstractIqPacketParser {
- protected static Element findRequiredChild(Element element, String elementName, String namespace) throws MissingRequiredElementException {
- if (null == element) {
- return null;
- }
- Element child = element.findChild(elementName, namespace);
+public abstract class IqPacketParser {
+ public static Element findRequiredChild(Element element, String elementName, String namespace) throws MissingRequiredElementException {
+ Element child = findChild(element, elementName, namespace);
if (child == null) {
throw new MissingRequiredElementException(elementName, namespace, element);
}
return child;
}
- protected static String findRequiredChildContent(Element element, String elementName) throws MissingRequiredContentException {
+ public static Element findChild(Element element, String elementName, String namespace) {
+ if (null == element) {
+ return null;
+ }
+ return element.findChild(elementName, namespace);
+ }
+
+ public static String findRequiredChildContent(Element element, String elementName) throws MissingRequiredContentException {
if (null == element) {
return null;
}
@@ -30,14 +34,18 @@ public abstract class AbstractIqPacketParser {
return childContent;
}
- protected static String findRequiredChildContent(Element element, String elementName, String namespace) throws MissingRequiredContentException {
- if (null == element) {
- return null;
- }
- String childContent = element.findChildContent(elementName, namespace);
+ public static String findRequiredChildContent(Element element, String elementName, String namespace) throws MissingRequiredContentException {
+ String childContent = findChildContent(element, elementName, namespace);
if (null == childContent) {
throw new MissingRequiredContentException(elementName, namespace, element);
}
return childContent;
}
+
+ public static String findChildContent(Element element, String elementName, String namespace) {
+ if (null == element) {
+ return null;
+ }
+ return element.findChildContent(elementName, namespace);
+ }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/BadRequestIqErrorException.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/BadRequestIqErrorException.java
new file mode 100644
index 00000000..69fb893b
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/BadRequestIqErrorException.java
@@ -0,0 +1,17 @@
+package de.thedevstack.conversationsplus.xmpp.exceptions;
+
+import de.thedevstack.conversationsplus.xml.Element;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public class BadRequestIqErrorException extends IqPacketErrorException {
+ public BadRequestIqErrorException(Element context, String errorMessage) {
+ super(context, errorMessage);
+ }
+
+ @Override
+ public String getMessage() {
+ return "Bad Request: " + super.getMessage();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/InternalServerErrorException.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/InternalServerErrorException.java
new file mode 100644
index 00000000..7925c423
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/InternalServerErrorException.java
@@ -0,0 +1,17 @@
+package de.thedevstack.conversationsplus.xmpp.exceptions;
+
+import de.thedevstack.conversationsplus.xml.Element;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public class InternalServerErrorException extends IqPacketErrorException {
+ public InternalServerErrorException(Element packet, String errorText) {
+ super(packet, errorText);
+ }
+
+ @Override
+ public String getMessage() {
+ return "Internal Server Error: " + super.getMessage();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/IqPacketErrorException.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/IqPacketErrorException.java
new file mode 100644
index 00000000..d4d932b7
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/IqPacketErrorException.java
@@ -0,0 +1,20 @@
+package de.thedevstack.conversationsplus.xmpp.exceptions;
+
+import de.thedevstack.conversationsplus.xml.Element;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public class IqPacketErrorException extends XmppException {
+ private final String iqErrorText;
+
+ public IqPacketErrorException(Element context, String errorMessage) {
+ super(context);
+ this.iqErrorText = errorMessage;
+ }
+
+ @Override
+ public String getMessage() {
+ return this.iqErrorText + " in context: " + this.getContext();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/ServiceUnavailableException.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/ServiceUnavailableException.java
new file mode 100644
index 00000000..7e503aa9
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/ServiceUnavailableException.java
@@ -0,0 +1,17 @@
+package de.thedevstack.conversationsplus.xmpp.exceptions;
+
+import de.thedevstack.conversationsplus.xml.Element;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public class ServiceUnavailableException extends IqPacketErrorException {
+ public ServiceUnavailableException(Element context, String errorMessage) {
+ super(context, errorMessage);
+ }
+
+ @Override
+ public String getMessage() {
+ return "Service unavailable: " + super.getMessage();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/UndefinedConditionException.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/UndefinedConditionException.java
new file mode 100644
index 00000000..38e1f6d2
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/UndefinedConditionException.java
@@ -0,0 +1,17 @@
+package de.thedevstack.conversationsplus.xmpp.exceptions;
+
+import de.thedevstack.conversationsplus.xml.Element;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public class UndefinedConditionException extends IqPacketErrorException {
+ public UndefinedConditionException(Element packet, String errorText) {
+ super(packet, errorText);
+ }
+
+ @Override
+ public String getMessage() {
+ return "Undefined Condition: " + super.getMessage();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotPacketParser.java
new file mode 100644
index 00000000..d7e136fc
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotPacketParser.java
@@ -0,0 +1,28 @@
+package de.thedevstack.conversationsplus.xmpp.filetransfer.http;
+
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xmpp.IqPacketParser;
+import de.thedevstack.conversationsplus.xmpp.exceptions.UnexpectedIqPacketTypeException;
+import de.thedevstack.conversationsplus.xmpp.exceptions.XmppException;
+import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
+import de.thedevstack.conversationsplus.xmpp.utils.ErrorIqPacketExceptionHelper;
+
+/**
+ * Created by steckbrief on 21.08.2016.
+ */
+public class DeleteSlotPacketParser extends IqPacketParser {
+ public static String parseDeleteToken(IqPacket packet) throws XmppException {
+ String deletetoken = null;
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element slot = findRequiredChild(packet, "slot", FileTransferHttp.NAMESPACE);
+
+ deletetoken = findRequiredChildContent(slot, "deletetoken");
+ } else if (packet.getType() == IqPacket.TYPE.ERROR) {
+ ErrorIqPacketExceptionHelper.throwIqErrorException(packet);
+ } else {
+ throw new UnexpectedIqPacketTypeException(packet, packet.getType(), IqPacket.TYPE.RESULT, IqPacket.TYPE.ERROR);
+ }
+
+ return deletetoken;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotRequestPacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotRequestPacket.java
new file mode 100644
index 00000000..6adc3aac
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotRequestPacket.java
@@ -0,0 +1,33 @@
+package de.thedevstack.conversationsplus.xmpp.filetransfer.http;
+
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
+
+/**
+ * Created by steckbrief on 21.08.2016.
+ */
+public class DeleteSlotRequestPacket extends IqPacket {
+ public static final String ELEMENT_NAME = "request";
+ public static final String FILEURL_ELEMENT_NAME = "fileurl";
+ private Element requestElement;
+ private String fileurl;
+
+ private DeleteSlotRequestPacket() {
+ super(TYPE.GET);
+ this.requestElement = super.addChild(DeleteSlotRequestPacket.ELEMENT_NAME, FileTransferHttp.NAMESPACE);
+ this.requestElement.setAttribute("type", "delete");
+ }
+
+ public DeleteSlotRequestPacket(String fileurl) {
+ this();
+ this.setFileURL(fileurl);
+ }
+
+ public void setFileURL(String fileurl) {
+ if (null == fileurl || fileurl.isEmpty()) {
+ throw new IllegalArgumentException("fileurl must not be null or empty.");
+ }
+ this.fileurl = fileurl;
+ this.requestElement.addChild(FILEURL_ELEMENT_NAME).setContent(fileurl);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttp.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttp.java
new file mode 100644
index 00000000..28f4f870
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttp.java
@@ -0,0 +1,8 @@
+package de.thedevstack.conversationsplus.xmpp.filetransfer.http;
+
+/**
+ * Created by steckbrief on 21.08.2016.
+ */
+public interface FileTransferHttp {
+ String NAMESPACE = "urn:xmpp:filetransfer:http";
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttpDeleteSlotRequestPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttpDeleteSlotRequestPacketGenerator.java
new file mode 100644
index 00000000..bc4380fe
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttpDeleteSlotRequestPacketGenerator.java
@@ -0,0 +1,39 @@
+package de.thedevstack.conversationsplus.xmpp.filetransfer.http;
+
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket;
+
+/**
+ * Created by steckbrief on 21.08.2016.
+ */
+public final class FileTransferHttpDeleteSlotRequestPacketGenerator {
+ /**
+ * Generates the IqPacket to request a slot to delete a file previously uploaded via http upload.
+ * The attributes from and id are not set in here - this is added while sending the packet.
+ * <pre>
+ * <iq from='romeo@montague.tld/garden'
+ * id='step_01'
+ * to='upload.montague.tld'
+ * type='get'>
+ * <request type='delete' xmlns='urn:xmpp:filetransfer:http'>
+ * <fileurl>http://upload.montague.tld/files/1e56ee17-ee4c-4a9c-aedd-cb09cb3984a7/my_juliet.png</fileurl>
+ * </request>
+ * </iq>
+ * </pre>
+ * @param host the jid of the host to request a slot from
+ * @param fileurl the URL of the file to be deleted
+ * @return the IqPacket
+ */
+ public static IqPacket generate(Jid host, String fileurl) {
+ DeleteSlotRequestPacket packet = new DeleteSlotRequestPacket(fileurl);
+ packet.setTo(host);
+ return packet;
+ }
+
+ /**
+ * Utility class - avoid instantiation
+ */
+ private FileTransferHttpDeleteSlotRequestPacketGenerator() {
+ // Helper class - avoid instantiation
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java
new file mode 100644
index 00000000..f02d8d46
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java
@@ -0,0 +1,44 @@
+package de.thedevstack.conversationsplus.xmpp.utils;
+
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xmpp.IqPacketParser;
+import de.thedevstack.conversationsplus.xmpp.exceptions.BadRequestIqErrorException;
+import de.thedevstack.conversationsplus.xmpp.exceptions.InternalServerErrorException;
+import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException;
+import de.thedevstack.conversationsplus.xmpp.exceptions.ServiceUnavailableException;
+import de.thedevstack.conversationsplus.xmpp.exceptions.UndefinedConditionException;
+
+/**
+ * Created by steckbrief on 22.08.2016.
+ */
+public final class ErrorIqPacketExceptionHelper {
+ private final static String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
+
+ public static void throwIqErrorException(Element packet) throws IqPacketErrorException {
+ if (hasErrorElement(packet, "bad-request")) {
+ throw new BadRequestIqErrorException(packet, getErrorText(packet));
+ }
+ if (hasErrorElement(packet, "service-unavailable")) {
+ throw new ServiceUnavailableException(packet, getErrorText(packet));
+ }
+ if (hasErrorElement(packet, "internal-server-error")) {
+ throw new InternalServerErrorException(packet, getErrorText(packet));
+ }
+ if (hasErrorElement(packet, "undefined-condition")) {
+ throw new UndefinedConditionException(packet, getErrorText(packet));
+ }
+ throw new IqPacketErrorException(packet, "Unknown error packet.");
+ }
+
+ private static boolean hasErrorElement(Element packet, String elementName) {
+ return null != IqPacketParser.findChild(packet, elementName, ERROR_NAMESPACE);
+ }
+
+ private static String getErrorText(Element packet) {
+ return IqPacketParser.findChildContent(packet, "text", ERROR_NAMESPACE);
+ }
+
+ private ErrorIqPacketExceptionHelper() {
+
+ }
+}
diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml
index 0c7d8eef..da7f0636 100644
--- a/src/main/res/menu/message_context.xml
+++ b/src/main/res/menu/message_context.xml
@@ -36,4 +36,8 @@
android:id="@+id/delete_file"
android:title="@string/delete_x_file"
android:visible="false"/>
+ <item
+ android:id="@+id/msg_ctx_menu_delete_remote_file"
+ android:title="@string/delete_remote_file_x"
+ android:visible="false"/>
</menu> \ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 3da33010..22b6c490 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -674,4 +674,5 @@
<string name="security_error_invalid_file_access">Security error: Invalid file access</string>
<string name="no_application_to_share_uri">No application found to share URI</string>
<string name="share_uri_with">Share URI with…</string>
+ <string name="delete_remote_file_x">Delete remote file</string>
</resources>