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    public void giveUpCurrentDecryption(){
 73        Message message;
 74        synchronized (this) {
 75            if(currentMessage != null) {
 76                return;
 77            }
 78            message = messages.peekFirst();
 79            if (message == null) {
 80                return;
 81            }
 82            discard(message);
 83        }
 84        synchronized (message){
 85            if (message.getEncryption() == Message.ENCRYPTION_PGP) {
 86                message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
 87            }
 88        }
 89        mXmppConnectionService.updateMessage(message);
 90        continueDecryption(true);
 91    }
 92
 93	protected synchronized void decryptNext() {
 94		if (pendingIntent == null
 95                && getOpenPgpApi() != null
 96                && (currentMessage =  messages.poll()) != null) {
 97			new Thread(new Runnable() {
 98                @Override
 99                public void run() {
100                    executeApi(currentMessage);
101                    decryptNext();
102                }
103            }).start();
104		}
105	}
106
107    public synchronized void continueDecryption(boolean resetPending) {
108        if (resetPending) {
109            this.pendingIntent = null;
110        }
111        continueDecryption();
112    }
113
114    public synchronized void continueDecryption() {
115        if (currentMessage == null) {
116            decryptNext();
117        }
118    }
119
120    private synchronized OpenPgpApi getOpenPgpApi() {
121        if (openPgpApi == null) {
122            this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
123        }
124        return this.openPgpApi;
125    }
126
127    private void executeApi(Message message) {
128        synchronized (message) {
129            Intent params = new Intent();
130            params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
131            if (message.getType() == Message.TYPE_TEXT) {
132                InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
133                final OutputStream os = new ByteArrayOutputStream();
134                Intent result = getOpenPgpApi().executeApi(params, is, os);
135                switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
136                    case OpenPgpApi.RESULT_CODE_SUCCESS:
137                        try {
138                            os.flush();
139                            final String body = os.toString();
140                            if (body == null) {
141                                throw new IOException("body was null");
142                            }
143                            message.setBody(body);
144                            message.setEncryption(Message.ENCRYPTION_DECRYPTED);
145                            final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
146                            if (message.trusted()
147                                    && message.treatAsDownloadable()
148                                    && manager.getAutoAcceptFileSize() > 0) {
149                                manager.createNewDownloadConnection(message);
150                            }
151                        } catch (IOException e) {
152                            message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
153                        }
154                        mXmppConnectionService.updateMessage(message);
155                        break;
156                    case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
157                        synchronized (PgpDecryptionService.this) {
158                            PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
159                            messages.addFirst(message);
160                            currentMessage = null;
161                            storePendingIntent(pendingIntent);
162                        }
163                        break;
164                    case OpenPgpApi.RESULT_CODE_ERROR:
165                        message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
166                        mXmppConnectionService.updateMessage(message);
167                        break;
168                }
169            } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
170                try {
171                    final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
172                    final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
173                    outputFile.getParentFile().mkdirs();
174                    outputFile.createNewFile();
175                    InputStream is = new FileInputStream(inputFile);
176                    OutputStream os = new FileOutputStream(outputFile);
177                    Intent result = getOpenPgpApi().executeApi(params, is, os);
178                    switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
179                        case OpenPgpApi.RESULT_CODE_SUCCESS:
180                            URL url = message.getFileParams().url;
181                            mXmppConnectionService.getFileBackend().updateFileParams(message, url);
182                            message.setEncryption(Message.ENCRYPTION_DECRYPTED);
183                            inputFile.delete();
184                            mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
185                            mXmppConnectionService.updateMessage(message);
186                            break;
187                        case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
188                            synchronized (PgpDecryptionService.this) {
189                                PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
190                                messages.addFirst(message);
191                                currentMessage = null;
192                                storePendingIntent(pendingIntent);
193                            }
194                            break;
195                        case OpenPgpApi.RESULT_CODE_ERROR:
196                            message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
197                            mXmppConnectionService.updateMessage(message);
198                            break;
199                    }
200                } catch (final IOException e) {
201                    message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
202                    mXmppConnectionService.updateMessage(message);
203                }
204            }
205        }
206        notifyIfPending(message);
207    }
208
209    private synchronized void notifyIfPending(Message message) {
210        if (pendingNotifications.remove(message)) {
211            mXmppConnectionService.getNotificationService().push(message);
212        }
213    }
214
215    private void storePendingIntent(PendingIntent pendingIntent) {
216        this.pendingIntent = pendingIntent;
217        mXmppConnectionService.updateConversationUi();
218    }
219
220    public synchronized boolean hasPendingIntent(Conversation conversation) {
221        if (pendingIntent == null) {
222            return false;
223        } else {
224            for(Message message : messages) {
225                if (message.getConversation() == conversation) {
226                    return true;
227                }
228            }
229            return false;
230        }
231    }
232
233    public PendingIntent getPendingIntent() {
234        return pendingIntent;
235    }
236
237    public boolean isConnected() {
238        return getOpenPgpApi() != null;
239    }
240}