~singpolyma/cheogram-android

9292bd16c000cb3a945cb08194b03defbf08498e — Stephen Paul Weber 3 months ago 18e81af + 34dd66c
Merge branch 'bob1'

* bob1:
  Preliminary support for bits-of-binary
A src/cheogram/java/com/cheogram/android/BobTransfer.java => src/cheogram/java/com/cheogram/android/BobTransfer.java +129 -0
@@ 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();
	}
}

M src/main/java/eu/siacs/conversations/entities/Message.java => src/main/java/eu/siacs/conversations/entities/Message.java +1 -1
@@ 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();
            }
        }

M src/main/java/eu/siacs/conversations/http/URL.java => src/main/java/eu/siacs/conversations/http/URL.java +1 -1
@@ 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;

M src/main/java/eu/siacs/conversations/parser/MessageParser.java => src/main/java/eu/siacs/conversations/parser/MessageParser.java +14 -1
@@ 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);

M src/main/java/eu/siacs/conversations/ui/ConversationFragment.java => src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +16 -3
@@ 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")

M src/main/java/eu/siacs/conversations/utils/MessageUtils.java => src/main/java/eu/siacs/conversations/utils/MessageUtils.java +2 -1
@@ 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")));
    }
}