PgpDecryptionService.java

  1package eu.siacs.conversations.crypto;
  2
  3import android.app.PendingIntent;
  4import android.content.Intent;
  5
  6import org.openintents.openpgp.util.OpenPgpApi;
  7
  8import java.io.ByteArrayInputStream;
  9import java.io.ByteArrayOutputStream;
 10import java.io.FileInputStream;
 11import java.io.FileOutputStream;
 12import java.io.IOException;
 13import java.io.InputStream;
 14import java.io.OutputStream;
 15import java.net.URL;
 16import java.util.ArrayDeque;
 17import java.util.HashSet;
 18import java.util.List;
 19
 20import eu.siacs.conversations.entities.Conversation;
 21import eu.siacs.conversations.entities.DownloadableFile;
 22import eu.siacs.conversations.entities.Message;
 23import eu.siacs.conversations.http.HttpConnectionManager;
 24import eu.siacs.conversations.services.XmppConnectionService;
 25
 26public class PgpDecryptionService {
 27
 28    private final XmppConnectionService mXmppConnectionService;
 29    private OpenPgpApi openPgpApi = null;
 30
 31	protected final ArrayDeque<Message> messages = new ArrayDeque();
 32    protected final HashSet<Message> pendingNotifications = new HashSet<>();
 33	Message currentMessage;
 34    private PendingIntent pendingIntent;
 35
 36
 37    public PgpDecryptionService(XmppConnectionService service) {
 38        this.mXmppConnectionService = service;
 39    }
 40
 41	public synchronized boolean decrypt(final Message message, boolean notify) {
 42        messages.add(message);
 43        if (notify && pendingIntent == null) {
 44            pendingNotifications.add(message);
 45            continueDecryption();
 46            return false;
 47        } else {
 48            continueDecryption();
 49            return notify;
 50        }
 51	}
 52
 53    public synchronized void decrypt(final List<Message> list) {
 54        for(Message message : list) {
 55            if (message.getEncryption() == Message.ENCRYPTION_PGP) {
 56                messages.add(message);
 57            }
 58        }
 59        continueDecryption();
 60    }
 61
 62    public synchronized void discard(List<Message> discards) {
 63        this.messages.removeAll(discards);
 64        this.pendingNotifications.removeAll(discards);
 65    }
 66
 67    public synchronized void discard(Message message) {
 68        this.messages.remove(message);
 69        this.pendingNotifications.remove(message);
 70    }
 71
 72	protected synchronized void decryptNext() {
 73		if (pendingIntent == null
 74                && getOpenPgpApi() != null
 75                && (currentMessage =  messages.poll()) != null) {
 76			new Thread(new Runnable() {
 77                @Override
 78                public void run() {
 79                    executeApi(currentMessage);
 80                    decryptNext();
 81                }
 82            }).start();
 83		}
 84	}
 85
 86    public synchronized void continueDecryption(boolean resetPending) {
 87        if (resetPending) {
 88            this.pendingIntent = null;
 89        }
 90        continueDecryption();
 91    }
 92
 93    public synchronized void continueDecryption() {
 94        if (currentMessage == null) {
 95            decryptNext();
 96        }
 97    }
 98
 99    private synchronized OpenPgpApi getOpenPgpApi() {
100        if (openPgpApi == null) {
101            this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
102        }
103        return this.openPgpApi;
104    }
105
106    private void executeApi(Message message) {
107        synchronized (message) {
108            Intent params = new Intent();
109            params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
110            if (message.getType() == Message.TYPE_TEXT) {
111                InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
112                final OutputStream os = new ByteArrayOutputStream();
113                Intent result = getOpenPgpApi().executeApi(params, is, os);
114                switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
115                    case OpenPgpApi.RESULT_CODE_SUCCESS:
116                        try {
117                            os.flush();
118                            final String body = os.toString();
119                            if (body == null) {
120                                throw new IOException("body was null");
121                            }
122                            message.setBody(body);
123                            message.setEncryption(Message.ENCRYPTION_DECRYPTED);
124                            final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
125                            if (message.trusted()
126                                    && message.treatAsDownloadable()
127                                    && manager.getAutoAcceptFileSize() > 0) {
128                                manager.createNewDownloadConnection(message);
129                            }
130                        } catch (IOException e) {
131                            message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
132                        }
133                        mXmppConnectionService.updateMessage(message);
134                        break;
135                    case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
136                        synchronized (PgpDecryptionService.this) {
137                            PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
138                            messages.addFirst(message);
139                            currentMessage = null;
140                            storePendingIntent(pendingIntent);
141                        }
142                        break;
143                    case OpenPgpApi.RESULT_CODE_ERROR:
144                        message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
145                        mXmppConnectionService.updateMessage(message);
146                        break;
147                }
148            } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
149                try {
150                    final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
151                    final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
152                    outputFile.getParentFile().mkdirs();
153                    outputFile.createNewFile();
154                    InputStream is = new FileInputStream(inputFile);
155                    OutputStream os = new FileOutputStream(outputFile);
156                    Intent result = getOpenPgpApi().executeApi(params, is, os);
157                    switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
158                        case OpenPgpApi.RESULT_CODE_SUCCESS:
159                            URL url = message.getFileParams().url;
160                            mXmppConnectionService.getFileBackend().updateFileParams(message, url);
161                            message.setEncryption(Message.ENCRYPTION_DECRYPTED);
162                            inputFile.delete();
163                            mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
164                            mXmppConnectionService.updateMessage(message);
165                            break;
166                        case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
167                            synchronized (PgpDecryptionService.this) {
168                                PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
169                                messages.addFirst(message);
170                                currentMessage = null;
171                                storePendingIntent(pendingIntent);
172                            }
173                            break;
174                        case OpenPgpApi.RESULT_CODE_ERROR:
175                            message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
176                            mXmppConnectionService.updateMessage(message);
177                            break;
178                    }
179                } catch (final IOException e) {
180                    message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
181                    mXmppConnectionService.updateMessage(message);
182                }
183            }
184        }
185        notifyIfPending(message);
186    }
187
188    private synchronized void notifyIfPending(Message message) {
189        if (pendingNotifications.remove(message)) {
190            mXmppConnectionService.getNotificationService().push(message);
191        }
192    }
193
194    private void storePendingIntent(PendingIntent pendingIntent) {
195        this.pendingIntent = pendingIntent;
196        mXmppConnectionService.updateConversationUi();
197    }
198
199    public synchronized boolean hasPendingIntent(Conversation conversation) {
200        if (pendingIntent == null) {
201            return false;
202        } else {
203            for(Message message : messages) {
204                if (message.getConversation() == conversation) {
205                    return true;
206                }
207            }
208            return false;
209        }
210    }
211
212    public PendingIntent getPendingIntent() {
213        return pendingIntent;
214    }
215
216    public boolean isConnected() {
217        return getOpenPgpApi() != null;
218    }
219}