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	protected final ArrayDeque<Message> messages = new ArrayDeque();
 29	protected final HashSet<Message> pendingNotifications = new HashSet<>();
 30	private final XmppConnectionService mXmppConnectionService;
 31	private OpenPgpApi openPgpApi = null;
 32	private Message currentMessage;
 33	private PendingIntent pendingIntent;
 34	private Intent userInteractionResult;
 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(Intent userInteractionResult) {
115		this.pendingIntent = null;
116		this.userInteractionResult = userInteractionResult;
117		continueDecryption();
118	}
119
120	public synchronized void continueDecryption() {
121		if (currentMessage == null) {
122			decryptNext();
123		}
124	}
125
126	private synchronized OpenPgpApi getOpenPgpApi() {
127		if (openPgpApi == null) {
128			this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
129		}
130		return this.openPgpApi;
131	}
132
133	private void executeApi(Message message) {
134		synchronized (message) {
135			Intent params = userInteractionResult != null ? userInteractionResult : new Intent();
136			params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
137			if (message.getType() == Message.TYPE_TEXT) {
138				InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
139				final OutputStream os = new ByteArrayOutputStream();
140				Intent result = getOpenPgpApi().executeApi(params, is, os);
141				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
142					case OpenPgpApi.RESULT_CODE_SUCCESS:
143						try {
144							os.flush();
145							final String body = os.toString();
146							if (body == null) {
147								throw new IOException("body was null");
148							}
149							message.setBody(body);
150							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
151							final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
152							if (message.trusted()
153									&& message.treatAsDownloadable()
154									&& manager.getAutoAcceptFileSize() > 0) {
155								manager.createNewDownloadConnection(message);
156							}
157						} catch (IOException e) {
158							message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
159						}
160						mXmppConnectionService.updateMessage(message);
161						break;
162					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
163						synchronized (PgpDecryptionService.this) {
164							PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
165							messages.addFirst(message);
166							currentMessage = null;
167							storePendingIntent(pendingIntent);
168						}
169						break;
170					case OpenPgpApi.RESULT_CODE_ERROR:
171						message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
172						mXmppConnectionService.updateMessage(message);
173						break;
174				}
175			} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
176				try {
177					final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
178					final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
179					outputFile.getParentFile().mkdirs();
180					outputFile.createNewFile();
181					InputStream is = new FileInputStream(inputFile);
182					OutputStream os = new FileOutputStream(outputFile);
183					Intent result = getOpenPgpApi().executeApi(params, is, os);
184					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
185						case OpenPgpApi.RESULT_CODE_SUCCESS:
186							URL url = message.getFileParams().url;
187							mXmppConnectionService.getFileBackend().updateFileParams(message, url);
188							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
189							inputFile.delete();
190							mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
191							mXmppConnectionService.updateMessage(message);
192							break;
193						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
194							synchronized (PgpDecryptionService.this) {
195								PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
196								messages.addFirst(message);
197								currentMessage = null;
198								storePendingIntent(pendingIntent);
199							}
200							break;
201						case OpenPgpApi.RESULT_CODE_ERROR:
202							message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
203							mXmppConnectionService.updateMessage(message);
204							break;
205					}
206				} catch (final IOException e) {
207					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
208					mXmppConnectionService.updateMessage(message);
209				}
210			}
211		}
212		notifyIfPending(message);
213	}
214
215	private synchronized void notifyIfPending(Message message) {
216		if (pendingNotifications.remove(message)) {
217			mXmppConnectionService.getNotificationService().push(message);
218		}
219	}
220
221	private void storePendingIntent(PendingIntent pendingIntent) {
222		this.pendingIntent = pendingIntent;
223		mXmppConnectionService.updateConversationUi();
224	}
225
226	public synchronized boolean hasPendingIntent(Conversation conversation) {
227		if (pendingIntent == null) {
228			return false;
229		} else {
230			for (Message message : messages) {
231				if (message.getConversation() == conversation) {
232					return true;
233				}
234			}
235			return false;
236		}
237	}
238
239	public PendingIntent getPendingIntent() {
240		return pendingIntent;
241	}
242
243	public boolean isConnected() {
244		return getOpenPgpApi() != null;
245	}
246}