Merge branch 'bob1'

Stephen Paul Weber created

* bob1:
  Preliminary support for bits-of-binary

Change summary

src/cheogram/java/com/cheogram/android/BobTransfer.java           | 129 +
src/main/java/eu/siacs/conversations/entities/Message.java        |   2 
src/main/java/eu/siacs/conversations/http/URL.java                |   2 
src/main/java/eu/siacs/conversations/parser/MessageParser.java    |  15 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java |  19 
src/main/java/eu/siacs/conversations/utils/MessageUtils.java      |   3 
6 files changed, 163 insertions(+), 7 deletions(-)

Detailed changes

src/cheogram/java/com/cheogram/android/BobTransfer.java 🔗

@@ -0,0 +1,129 @@
+package com.cheogram.android;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.DownloadableFile;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.http.AesGcmURL;
+import eu.siacs.conversations.services.AbstractConnectionManager;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.utils.MimeUtils;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class BobTransfer implements Transferable {
+	protected int status = Transferable.STATUS_OFFER;
+	protected Message message;
+	protected URI uri;
+	protected XmppConnectionService xmppConnectionService;
+	protected DownloadableFile file;
+
+	public BobTransfer(Message message, XmppConnectionService xmppConnectionService) throws URISyntaxException {
+		this.message = message;
+		this.xmppConnectionService = xmppConnectionService;
+		this.uri = new URI(message.getFileParams().url);
+		setupFile();
+	}
+
+	private void setupFile() {
+		final String reference = uri.getFragment();
+		if (reference != null && AesGcmURL.IV_KEY.matcher(reference).matches()) {
+			this.file = new DownloadableFile(xmppConnectionService.getCacheDir(), message.getUuid());
+			this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
+			Log.d(Config.LOGTAG, "create temporary OMEMO encrypted file: " + this.file.getAbsolutePath() + "(" + message.getMimeType() + ")");
+		} else {
+			this.file = xmppConnectionService.getFileBackend().getFile(message, false);
+		}
+	}
+
+	@Override
+	public boolean start() {
+		if (status == Transferable.STATUS_DOWNLOADING) return true;
+
+		if (xmppConnectionService.hasInternetConnection()) {
+			changeStatus(Transferable.STATUS_DOWNLOADING);
+
+			IqPacket request = new IqPacket(IqPacket.TYPE.GET);
+			request.setTo(message.getCounterpart());
+			final Element dataq = request.addChild("data", "urn:xmpp:bob");
+			dataq.setAttribute("cid", uri.getSchemeSpecificPart());
+			xmppConnectionService.sendIqPacket(message.getConversation().getAccount(), request, (acct, packet) -> {
+				final Element data = packet.findChild("data", "urn:xmpp:bob");
+				if (packet.getType() == IqPacket.TYPE.ERROR || data == null) {
+					Log.d(Config.LOGTAG, "BobTransfer failed: " + packet);
+					xmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found);
+				} else {
+					final String contentType = data.getAttribute("type");
+					if (contentType != null) {
+						final String fileExtension = MimeUtils.guessExtensionFromMimeType(contentType);
+						if (fileExtension != null) {
+							xmppConnectionService.getFileBackend().setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), fileExtension), contentType);
+							Log.d(Config.LOGTAG, "rewriting name for bob based on content type");
+							setupFile();
+						}
+					}
+
+					try {
+						file.getParentFile().mkdirs();
+						if (!file.exists() && !file.createNewFile()) {
+							throw new IOException(file.getAbsolutePath());
+						}
+						final OutputStream outputStream = AbstractConnectionManager.createOutputStream(file, false, false);
+						outputStream.write(Base64.decode(data.getContent(), Base64.DEFAULT));
+						outputStream.flush();
+						outputStream.close();
+
+						final boolean privateMessage = message.isPrivateMessage();
+						message.setType(privateMessage ? Message.TYPE_PRIVATE_FILE : Message.TYPE_FILE);
+						xmppConnectionService.getFileBackend().updateFileParams(message, uri.toString());
+						xmppConnectionService.updateMessage(message);
+					} catch (IOException e) {
+						xmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_write_file);
+					}
+				}
+				message.setTransferable(null);
+				xmppConnectionService.updateConversationUi();
+			});
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	@Override
+	public int getStatus() {
+		return status;
+	}
+
+	@Override
+	public int getProgress() {
+		return 0;
+	}
+
+	@Override
+	public Long getFileSize() {
+		return null;
+	}
+
+	@Override
+	public void cancel() {
+		// No real way to cancel an iq in process...
+		changeStatus(Transferable.STATUS_CANCELLED);
+		message.setTransferable(null);
+	}
+
+	protected void changeStatus(int newStatus) {
+		status = newStatus;
+		xmppConnectionService.updateConversationUi();
+	}
+}

src/main/java/eu/siacs/conversations/entities/Message.java 🔗

@@ -930,7 +930,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
                 fileParams.size = this.transferable.getFileSize();
             }
 
-            if (oobUri != null && ("http".equalsIgnoreCase(oobUri.getScheme()) || "https".equalsIgnoreCase(oobUri.getScheme()))) {
+            if (oobUri != null && ("http".equalsIgnoreCase(oobUri.getScheme()) || "https".equalsIgnoreCase(oobUri.getScheme()) || "cid".equalsIgnoreCase(oobUri.getScheme()))) {
                 fileParams.url = oobUri.toString();
             }
         }

src/main/java/eu/siacs/conversations/http/URL.java 🔗

@@ -9,7 +9,7 @@ import okhttp3.HttpUrl;
 
 public class URL {
 
-    public static final List<String> WELL_KNOWN_SCHEMES = Arrays.asList("http", "https", AesGcmURL.PROTOCOL_NAME);
+    public static final List<String> WELL_KNOWN_SCHEMES = Arrays.asList("http", "https", AesGcmURL.PROTOCOL_NAME, "cid");
 
     public static String tryParse(String url) {
         final URI uri;

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -3,6 +3,9 @@ package eu.siacs.conversations.parser;
 import android.util.Log;
 import android.util.Pair;
 
+import com.cheogram.android.BobTransfer;
+
+import java.net.URISyntaxException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -746,7 +749,17 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
             mXmppConnectionService.databaseBackend.createMessage(message);
             final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
             if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
-                manager.createNewDownloadConnection(message);
+                if (message.getOob() != null && message.getOob().getScheme().equalsIgnoreCase("cid")) {
+                    try {
+                        BobTransfer transfer = new BobTransfer(message, mXmppConnectionService);
+                        message.setTransferable(transfer);
+                        transfer.start();
+                    } catch (URISyntaxException e) {
+                        Log.d(Config.LOGTAG, "BobTransfer failed to parse URI");
+                    }
+                } else {
+                    manager.createNewDownloadConnection(message);
+                }
             } else if (notify) {
                 if (query != null && query.isCatchup()) {
                     mXmppConnectionService.getNotificationService().pushFromBacklog(message);

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -64,11 +64,14 @@ import androidx.databinding.DataBindingUtil;
 import androidx.viewpager.widget.PagerAdapter;
 import androidx.viewpager.widget.ViewPager;
 
+import com.cheogram.android.BobTransfer;
+
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 
 import org.jetbrains.annotations.NotNull;
 
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -1925,9 +1928,19 @@ public class ConversationFragment extends XmppFragment
                     .show();
             return;
         }
-        activity.xmppConnectionService
-                .getHttpConnectionManager()
-                .createNewDownloadConnection(message, true);
+        if (message.getOob() != null && message.getOob().getScheme().equalsIgnoreCase("cid")) {
+            try {
+                BobTransfer transfer = new BobTransfer(message, activity.xmppConnectionService);
+                message.setTransferable(transfer);
+                transfer.start();
+            } catch (URISyntaxException e) {
+                Log.d(Config.LOGTAG, "BobTransfer failed to parse URI");
+            }
+        } else {
+            activity.xmppConnectionService
+                    .getHttpConnectionManager()
+                    .createNewDownloadConnection(message, true);
+        }
     }
 
     @SuppressLint("InflateParams")

src/main/java/eu/siacs/conversations/utils/MessageUtils.java 🔗

@@ -117,6 +117,7 @@ public class MessageUtils {
     }
 
     public static boolean unInitiatedButKnownSize(Message message) {
-        return message.getType() == Message.TYPE_TEXT && message.getTransferable() == null && message.isOOb() && message.getFileParams().size != null && message.getFileParams().url != null;
+        return message.getType() == Message.TYPE_TEXT && message.getTransferable() == null && message.isOOb() && message.getFileParams().url != null &&
+               (message.getFileParams().size != null || (message.getOob() != null && message.getOob().getScheme().equalsIgnoreCase("cid")));
     }
 }