~singpolyma/cheogram-android

a8e57ce1c9a9603dbb0f22b8dd6d924016364609 — Stephen Paul Weber 2 years ago 4f4a5bb
Fetch XEP-0231 inline images from trusted contacts

If a contact is trusted or already likely to know our presence (due to having
sent them a message) then auto-fetch XHTML-IM inline images via XEP-0231 from them.
M src/cheogram/java/com/cheogram/android/BobTransfer.java => src/cheogram/java/com/cheogram/android/BobTransfer.java +50 -22
@@ 15,6 15,7 @@ import io.ipfs.cid.Cid;

import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Transferable;


@@ 24,12 25,14 @@ 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.Jid;
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 Account account;
	protected Jid to;
	protected XmppConnectionService xmppConnectionService;

	public static Cid cid(URI uri) {


@@ 44,10 47,15 @@ public class BobTransfer implements Transferable {
		}
	}

	public BobTransfer(Message message, XmppConnectionService xmppConnectionService) throws URISyntaxException {
		this.message = message;
	public static URI uri(Cid cid) throws NoSuchAlgorithmException, URISyntaxException {
		return new URI("cid", CryptoHelper.multihashAlgo(cid.getType()) + "+" + CryptoHelper.bytesToHex(cid.getHash()) + "@bob.xmpp.org", null);
	}

	public BobTransfer(URI uri, Account account, Jid to, XmppConnectionService xmppConnectionService) {
		this.xmppConnectionService = xmppConnectionService;
		this.uri = new URI(message.getFileParams().url);
		this.uri = uri;
		this.to = to;
		this.account = account;
	}

	@Override


@@ 56,10 64,7 @@ public class BobTransfer implements Transferable {
		File f = xmppConnectionService.getFileForCid(cid(uri));

		if (f != null && f.canRead()) {
			message.setRelativeFilePath(f.getAbsolutePath());
			finish();
			message.setTransferable(null);
			xmppConnectionService.updateConversationUi();
			finish(f);
			return true;
		}



@@ 67,13 72,14 @@ public class BobTransfer implements Transferable {
			changeStatus(Transferable.STATUS_DOWNLOADING);

			IqPacket request = new IqPacket(IqPacket.TYPE.GET);
			request.setTo(message.getCounterpart());
			request.setTo(to);
			final Element dataq = request.addChild("data", "urn:xmpp:bob");
			dataq.setAttribute("cid", uri.getSchemeSpecificPart());
			xmppConnectionService.sendIqPacket(message.getConversation().getAccount(), request, (acct, packet) -> {
			xmppConnectionService.sendIqPacket(account, 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);
					finish(null);
					xmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found);
				} else {
					final String contentType = data.getAttribute("type");


@@ 85,25 91,23 @@ public class BobTransfer implements Transferable {
					try {
						final byte[] bytes = Base64.decode(data.getContent(), Base64.DEFAULT);

						xmppConnectionService.getFileBackend().setupRelativeFilePath(message, new ByteArrayInputStream(bytes), fileExtension);
						DownloadableFile file = xmppConnectionService.getFileBackend().getFile(message);
						File file = xmppConnectionService.getFileBackend().getStorageLocation(new ByteArrayInputStream(bytes), fileExtension);
						file.getParentFile().mkdirs();
						if (!file.exists() && !file.createNewFile()) {
							throw new IOException(file.getAbsolutePath());
						}

						final OutputStream outputStream = AbstractConnectionManager.createOutputStream(file, false, false);
						final OutputStream outputStream = AbstractConnectionManager.createOutputStream(new DownloadableFile(file.getAbsolutePath()), false, false);
						outputStream.write(bytes);
						outputStream.flush();
						outputStream.close();

						finish();
						finish(file);
					} catch (IOException e) {
						finish(null);
						xmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_write_file);
					}
				}
				message.setTransferable(null);
				xmppConnectionService.updateConversationUi();
			});
			return true;
		} else {


@@ 130,7 134,6 @@ public class BobTransfer implements Transferable {
	public void cancel() {
		// No real way to cancel an iq in process...
		changeStatus(Transferable.STATUS_CANCELLED);
		message.setTransferable(null);
	}

	protected void changeStatus(int newStatus) {


@@ 138,10 141,35 @@ public class BobTransfer implements Transferable {
		xmppConnectionService.updateConversationUi();
	}

	protected void finish() {
		final boolean privateMessage = message.isPrivateMessage();
		message.setType(privateMessage ? Message.TYPE_PRIVATE_FILE : Message.TYPE_FILE);
		xmppConnectionService.getFileBackend().updateFileParams(message, uri.toString(), false);
		xmppConnectionService.updateMessage(message);
	protected void finish(File f) {
		if (f != null) xmppConnectionService.updateConversationUi();
	}

	public static class ForMessage extends BobTransfer {
		protected Message message;

		public ForMessage(Message message, XmppConnectionService xmppConnectionService) throws URISyntaxException {
			super(new URI(message.getFileParams().url), message.getConversation().getAccount(), message.getCounterpart(), xmppConnectionService);
			this.message = message;
		}

		@Override
		public void cancel() {
			super.cancel();
			message.setTransferable(null);
		}

		@Override
		protected void finish(File f) {
			if (f != null) {
				message.setRelativeFilePath(f.getAbsolutePath());
				final boolean privateMessage = message.isPrivateMessage();
				message.setType(privateMessage ? Message.TYPE_PRIVATE_FILE : Message.TYPE_FILE);
				xmppConnectionService.getFileBackend().updateFileParams(message, uri.toString(), false);
				xmppConnectionService.updateMessage(message);
			}
			message.setTransferable(null);
			super.finish(f);
		}
	}
}

M src/main/java/eu/siacs/conversations/entities/Contact.java => src/main/java/eu/siacs/conversations/entities/Contact.java +4 -0
@@ 414,6 414,10 @@ public class Contact implements ListItem, Blockable {
        return ((this.subscription & (1 << option)) != 0);
    }

    public boolean canInferPresence() {
        return showInContactList() || isSelf();
    }

    public boolean showInRoster() {
        return (this.getOption(Contact.Options.IN_ROSTER) && (!this
                .getOption(Contact.Options.DIRTY_DELETE)))

M src/main/java/eu/siacs/conversations/entities/Conversation.java => src/main/java/eu/siacs/conversations/entities/Conversation.java +6 -0
@@ 1130,6 1130,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
        return count;
    }

    public boolean canInferPresence() {
        final Contact contact = getContact();
        if (contact != null && contact.canInferPresence()) return true;
        return sentMessagesCount() > 0;
    }

    public boolean isWithStranger() {
        final Contact contact = getContact();
        return mode == MODE_SINGLE

M src/main/java/eu/siacs/conversations/entities/Conversational.java => src/main/java/eu/siacs/conversations/entities/Conversational.java +2 -0
@@ 45,4 45,6 @@ public interface Conversational {
	int getMode();

	String getUuid();

	boolean canInferPresence();
}

M src/main/java/eu/siacs/conversations/entities/StubConversation.java => src/main/java/eu/siacs/conversations/entities/StubConversation.java +6 -0
@@ 70,4 70,10 @@ public class StubConversation implements Conversational {
	public String getUuid() {
		return uuid;
	}

	@Override
	public boolean canInferPresence() {
		final Contact contact = getContact();
		return contact != null && contact.canInferPresence();
	}
}

M src/main/java/eu/siacs/conversations/parser/MessageParser.java => src/main/java/eu/siacs/conversations/parser/MessageParser.java +1 -1
@@ 765,7 765,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
            if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
                if (message.getOob() != null && message.getOob().getScheme().equalsIgnoreCase("cid")) {
                    try {
                        BobTransfer transfer = new BobTransfer(message, mXmppConnectionService);
                        BobTransfer transfer = new BobTransfer.ForMessage(message, mXmppConnectionService);
                        message.setTransferable(transfer);
                        transfer.start();
                    } catch (URISyntaxException e) {

M src/main/java/eu/siacs/conversations/persistance/FileBackend.java => src/main/java/eu/siacs/conversations/persistance/FileBackend.java +12 -7
@@ 887,13 887,7 @@ public class FileBackend {
    }

    public void setupRelativeFilePath(final Message message, final InputStream is, final String extension) throws IOException {
        Cid[] cids = calculateCids(is);

        setupRelativeFilePath(message, String.format("%s.%s", cids[0], extension));
        File file = getFile(message);
        for (int i = 0; i < cids.length; i++) {
            mXmppConnectionService.saveCid(cids[i], file);
        }
        message.setRelativeFilePath(getStorageLocation(is, extension).getAbsolutePath());
    }

    public void setupRelativeFilePath(final Message message, final String filename) {


@@ 902,6 896,17 @@ public class FileBackend {
        setupRelativeFilePath(message, filename, mime);
    }

    public File getStorageLocation(final InputStream is, final String extension) throws IOException {
        final String mime = MimeUtils.guessMimeTypeFromExtension(extension);
        Cid[] cids = calculateCids(is);

        File file = getStorageLocation(String.format("%s.%s", cids[0], extension), mime);
        for (int i = 0; i < cids.length; i++) {
            mXmppConnectionService.saveCid(cids[i], file);
        }
        return file;
    }

    public File getStorageLocation(final String filename, final String mime) {
        final File parentDirectory;
        if (Strings.isNullOrEmpty(mime)) {

M src/main/java/eu/siacs/conversations/ui/ConversationFragment.java => src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +1 -1
@@ 1946,7 1946,7 @@ public class ConversationFragment extends XmppFragment
        }
        if (message.getOob() != null && message.getOob().getScheme().equalsIgnoreCase("cid")) {
            try {
                BobTransfer transfer = new BobTransfer(message, activity.xmppConnectionService);
                BobTransfer transfer = new BobTransfer.ForMessage(message, activity.xmppConnectionService);
                message.setTransferable(transfer);
                transfer.start();
            } catch (URISyntaxException e) {

M src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java => src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +12 -1
@@ 36,10 36,14 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;

import com.cheogram.android.BobTransfer;

import com.google.common.base.Strings;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;


@@ 450,7 454,14 @@ public class MessageAdapter extends ArrayAdapter<Message> {
            SpannableStringBuilder body = message.getMergedBody((cid) -> {
                try {
                    DownloadableFile f = activity.xmppConnectionService.getFileForCid(cid);
                    if (f == null) return null;
                    if (f == null || !f.canRead()) {
                        if (!message.trusted() && !message.getConversation().canInferPresence()) return null;

                        try {
                            new BobTransfer(BobTransfer.uri(cid), message.getConversation().getAccount(), message.getCounterpart(), activity.xmppConnectionService).start();
                        } catch (final NoSuchAlgorithmException | URISyntaxException e) { }
                        return null;
                    }

                    Drawable d = activity.xmppConnectionService.getFileBackend().getThumbnail(f, activity.getResources(), (int) (metrics.density * 288), true);
                    if (d == null) {

M src/main/java/eu/siacs/conversations/utils/CryptoHelper.java => src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +13 -0
@@ 288,6 288,19 @@ public final class CryptoHelper {
        return !u.contains(" ") && (u.startsWith("https://") || u.startsWith("http://") || u.startsWith("p1s3://")) && u.endsWith(".pgp");
    }

    public static String multihashAlgo(Multihash.Type type) throws NoSuchAlgorithmException {
        switch(type) {
        case sha1:
            return "sha1";
        case sha2_256:
            return "sha-256";
        case sha2_512:
            return "sha-512";
        default:
            throw new NoSuchAlgorithmException("" + type);
        }
    }

    public static Multihash.Type multihashType(String algo) throws NoSuchAlgorithmException {
        if (algo.equals("SHA-1") || algo.equals("sha-1") || algo.equals("sha1")) {
            return Multihash.Type.sha1;