From 3ed7cb54e5858afaadc3f7ec5bc01edb61e1428e Mon Sep 17 00:00:00 2001 From: steckbrief Date: Mon, 22 Aug 2016 21:30:36 +0200 Subject: Basic filetransfer http delete implementation; Exceptions for IqPacketError added --- build.gradle | 4 +- libs/3rdParty/okhttp-3.4.1.jar | Bin 0 -> 344481 bytes libs/3rdParty/okio-1.9.0.jar | Bin 0 -> 76036 bytes .../ConversationsPlusApplication.java | 8 ++ .../conversationsplus/entities/Message.java | 3 + .../conversationsplus/entities/RemoteFile.java | 37 +++++++++ .../conversationsplus/http/HttpClient.java | 81 ++++++++++++++++++++ .../filetransfer/http/DeleteRemoteFile.java | 22 ++++++ .../filetransfer/http/DeleteRemoteFileService.java | 30 ++++++++ .../filetransfer/http/DeleteTokenReceived.java | 83 +++++++++++++++++++++ .../conversationsplus/ui/ConversationFragment.java | 11 ++- .../conversationsplus/utils/MessageUtil.java | 10 +++ .../xmpp/AbstractIqPacketParser.java | 43 ----------- .../conversationsplus/xmpp/IqPacketParser.java | 51 +++++++++++++ .../exceptions/BadRequestIqErrorException.java | 17 +++++ .../exceptions/InternalServerErrorException.java | 17 +++++ .../xmpp/exceptions/IqPacketErrorException.java | 20 +++++ .../exceptions/ServiceUnavailableException.java | 17 +++++ .../exceptions/UndefinedConditionException.java | 17 +++++ .../filetransfer/http/DeleteSlotPacketParser.java | 28 +++++++ .../filetransfer/http/DeleteSlotRequestPacket.java | 33 ++++++++ .../xmpp/filetransfer/http/FileTransferHttp.java | 8 ++ ...ansferHttpDeleteSlotRequestPacketGenerator.java | 39 ++++++++++ .../xmpp/utils/ErrorIqPacketExceptionHelper.java | 44 +++++++++++ src/main/res/menu/message_context.xml | 4 + src/main/res/values/strings.xml | 1 + 26 files changed, 581 insertions(+), 47 deletions(-) create mode 100644 libs/3rdParty/okhttp-3.4.1.jar create mode 100644 libs/3rdParty/okio-1.9.0.jar create mode 100644 src/main/java/de/thedevstack/conversationsplus/entities/RemoteFile.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/http/HttpClient.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFile.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteRemoteFileService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/DeleteTokenReceived.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractIqPacketParser.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketParser.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/BadRequestIqErrorException.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/InternalServerErrorException.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/IqPacketErrorException.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/ServiceUnavailableException.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/UndefinedConditionException.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotPacketParser.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/DeleteSlotRequestPacket.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttp.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/filetransfer/http/FileTransferHttpDeleteSlotRequestPacketGenerator.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java diff --git a/build.gradle b/build.gradle index f58ac6df..1218e605 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ apply plugin: 'com.android.application' repositories { flatDir { - dirs 'libs/3rdParty/jcenter', 'libs/3rdParty/maven' + dirs 'libs/3rdParty/jcenter', 'libs/3rdParty/maven', 'libs/3rdParty' } maven { url "http://snippets.thedevstack.de/mvn" @@ -48,6 +48,8 @@ dependencies { compile name: 'android-crop-1.0.1', ext: 'aar' compile name: 'roundedimageview-2.2.0', ext: 'aar' compile name: 'openpgp-api-10.0', ext: 'aar' // loaded from jcenter + compile name: 'okio-1.9.0' + compile name: 'okhttp-3.4.1' // Local modules compile project(':libs:MemorizingTrustManager') diff --git a/libs/3rdParty/okhttp-3.4.1.jar b/libs/3rdParty/okhttp-3.4.1.jar new file mode 100644 index 00000000..e31f2486 Binary files /dev/null and b/libs/3rdParty/okhttp-3.4.1.jar differ diff --git a/libs/3rdParty/okio-1.9.0.jar b/libs/3rdParty/okio-1.9.0.jar new file mode 100644 index 00000000..3c42b934 Binary files /dev/null and b/libs/3rdParty/okio-1.9.0.jar differ 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/AbstractIqPacketParser.java deleted file mode 100644 index d777cb64..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractIqPacketParser.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.thedevstack.conversationsplus.xmpp; - -import de.thedevstack.conversationsplus.xml.Element; -import de.thedevstack.conversationsplus.xmpp.exceptions.MissingRequiredContentException; -import de.thedevstack.conversationsplus.xmpp.exceptions.MissingRequiredElementException; - -/** - * - */ -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); - if (child == null) { - throw new MissingRequiredElementException(elementName, namespace, element); - } - return child; - } - - protected static String findRequiredChildContent(Element element, String elementName) throws MissingRequiredContentException { - if (null == element) { - return null; - } - String childContent = element.findChildContent(elementName); - if (null == childContent) { - throw new MissingRequiredContentException(elementName, element); - } - 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); - if (null == childContent) { - throw new MissingRequiredContentException(elementName, namespace, element); - } - return childContent; - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketParser.java new file mode 100644 index 00000000..7c198ce9 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketParser.java @@ -0,0 +1,51 @@ +package de.thedevstack.conversationsplus.xmpp; + +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.exceptions.MissingRequiredContentException; +import de.thedevstack.conversationsplus.xmpp.exceptions.MissingRequiredElementException; + +/** + * + */ +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; + } + + 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; + } + String childContent = element.findChildContent(elementName); + if (null == childContent) { + throw new MissingRequiredContentException(elementName, element); + } + return childContent; + } + + 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. + *
+     * 
+     *   
+     *     http://upload.montague.tld/files/1e56ee17-ee4c-4a9c-aedd-cb09cb3984a7/my_juliet.png
+     *   
+     * 
+     * 
+ * @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"/> + \ 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 @@ Security error: Invalid file access No application found to share URI Share URI with… + Delete remote file -- cgit v1.2.3