PgpDecryptionService.java

  1package eu.siacs.conversations.crypto;
  2
  3import android.app.PendingIntent;
  4import android.content.Intent;
  5import android.util.Log;
  6
  7import org.openintents.openpgp.OpenPgpMetadata;
  8import org.openintents.openpgp.util.OpenPgpApi;
  9
 10import java.io.ByteArrayInputStream;
 11import java.io.ByteArrayOutputStream;
 12import java.io.FileInputStream;
 13import java.io.FileOutputStream;
 14import java.io.IOException;
 15import java.io.InputStream;
 16import java.io.OutputStream;
 17import java.net.URL;
 18import java.util.ArrayDeque;
 19import java.util.HashSet;
 20import java.util.List;
 21
 22import eu.siacs.conversations.Config;
 23import eu.siacs.conversations.entities.Conversation;
 24import eu.siacs.conversations.entities.DownloadableFile;
 25import eu.siacs.conversations.entities.Message;
 26import eu.siacs.conversations.http.HttpConnectionManager;
 27import eu.siacs.conversations.services.XmppConnectionService;
 28import eu.siacs.conversations.utils.MimeUtils;
 29
 30public class PgpDecryptionService {
 31
 32	protected final ArrayDeque<Message> messages = new ArrayDeque();
 33	protected final HashSet<Message> pendingNotifications = new HashSet<>();
 34	private final XmppConnectionService mXmppConnectionService;
 35	private OpenPgpApi openPgpApi = null;
 36	private Message currentMessage;
 37	private PendingIntent pendingIntent;
 38	private Intent userInteractionResult;
 39
 40
 41	public PgpDecryptionService(XmppConnectionService service) {
 42		this.mXmppConnectionService = service;
 43	}
 44
 45	public synchronized boolean decrypt(final Message message, boolean notify) {
 46		messages.add(message);
 47		if (notify && pendingIntent == null) {
 48			pendingNotifications.add(message);
 49			continueDecryption();
 50			return false;
 51		} else {
 52			continueDecryption();
 53			return notify;
 54		}
 55	}
 56
 57	public synchronized void decrypt(final List<Message> list) {
 58		for (Message message : list) {
 59			if (message.getEncryption() == Message.ENCRYPTION_PGP) {
 60				messages.add(message);
 61			}
 62		}
 63		continueDecryption();
 64	}
 65
 66	public synchronized void discard(List<Message> discards) {
 67		this.messages.removeAll(discards);
 68		this.pendingNotifications.removeAll(discards);
 69	}
 70
 71	public synchronized void discard(Message message) {
 72		this.messages.remove(message);
 73		this.pendingNotifications.remove(message);
 74	}
 75
 76	public void giveUpCurrentDecryption() {
 77		Message message;
 78		synchronized (this) {
 79			if (currentMessage != null) {
 80				return;
 81			}
 82			message = messages.peekFirst();
 83			if (message == null) {
 84				return;
 85			}
 86			discard(message);
 87		}
 88		synchronized (message) {
 89			if (message.getEncryption() == Message.ENCRYPTION_PGP) {
 90				message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
 91			}
 92		}
 93		mXmppConnectionService.updateMessage(message, false);
 94		continueDecryption(true);
 95	}
 96
 97	protected synchronized void decryptNext() {
 98		if (pendingIntent == null
 99				&& getOpenPgpApi() != null
100				&& (currentMessage = messages.poll()) != null) {
101			new Thread(new Runnable() {
102				@Override
103				public void run() {
104					executeApi(currentMessage);
105					decryptNext();
106				}
107			}).start();
108		}
109	}
110
111	public synchronized void continueDecryption(boolean resetPending) {
112		if (resetPending) {
113			this.pendingIntent = null;
114		}
115		continueDecryption();
116	}
117
118	public synchronized void continueDecryption(Intent userInteractionResult) {
119		this.pendingIntent = null;
120		this.userInteractionResult = userInteractionResult;
121		continueDecryption();
122	}
123
124	public synchronized void continueDecryption() {
125		if (currentMessage == null) {
126			decryptNext();
127		}
128	}
129
130	private synchronized OpenPgpApi getOpenPgpApi() {
131		if (openPgpApi == null) {
132			this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
133		}
134		return this.openPgpApi;
135	}
136
137	private void executeApi(Message message) {
138		synchronized (message) {
139			Intent params = userInteractionResult != null ? userInteractionResult : new Intent();
140			params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
141			if (message.getType() == Message.TYPE_TEXT) {
142				InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
143				final OutputStream os = new ByteArrayOutputStream();
144				Intent result = getOpenPgpApi().executeApi(params, is, os);
145				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
146					case OpenPgpApi.RESULT_CODE_SUCCESS:
147						try {
148							os.flush();
149							final String body = os.toString();
150							if (body == null) {
151								throw new IOException("body was null");
152							}
153							message.setBody(body);
154							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
155							final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
156							if (message.trusted()
157									&& message.treatAsDownloadable()
158									&& manager.getAutoAcceptFileSize() > 0) {
159								manager.createNewDownloadConnection(message);
160							}
161						} catch (IOException e) {
162							message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
163						}
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			} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
180				try {
181					final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
182					final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
183					if (outputFile.getParentFile().mkdirs()) {
184						Log.d(Config.LOGTAG,"created parent directories for "+outputFile.getAbsolutePath());
185					}
186					outputFile.createNewFile();
187					InputStream is = new FileInputStream(inputFile);
188					OutputStream os = new FileOutputStream(outputFile);
189					Intent result = getOpenPgpApi().executeApi(params, is, os);
190					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
191						case OpenPgpApi.RESULT_CODE_SUCCESS:
192							OpenPgpMetadata openPgpMetadata = result.getParcelableExtra(OpenPgpApi.RESULT_METADATA);
193							String originalFilename = openPgpMetadata.getFilename();
194							String originalExtension = originalFilename == null ? null : MimeUtils.extractRelevantExtension(originalFilename);
195							if (originalExtension != null && MimeUtils.extractRelevantExtension(outputFile.getName()) == null) {
196								Log.d(Config.LOGTAG,"detected original filename during pgp decryption");
197								String mime = MimeUtils.guessMimeTypeFromExtension(originalExtension);
198								String path = outputFile.getName()+"."+originalExtension;
199								DownloadableFile fixedFile = mXmppConnectionService.getFileBackend().getFileForPath(path,mime);
200								if (fixedFile.getParentFile().mkdirs()) {
201									Log.d(Config.LOGTAG,"created parent directories for "+fixedFile.getAbsolutePath());
202								}
203								if (outputFile.renameTo(fixedFile)) {
204									Log.d(Config.LOGTAG, "renamed " + outputFile.getAbsolutePath() + " to " + fixedFile.getAbsolutePath());
205									message.setRelativeFilePath(path);
206								}
207							}
208							URL url = message.getFileParams().url;
209							mXmppConnectionService.getFileBackend().updateFileParams(message, url);
210							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
211							inputFile.delete();
212							mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
213							mXmppConnectionService.updateMessage(message);
214							break;
215						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
216							synchronized (PgpDecryptionService.this) {
217								PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
218								messages.addFirst(message);
219								currentMessage = null;
220								storePendingIntent(pendingIntent);
221							}
222							break;
223						case OpenPgpApi.RESULT_CODE_ERROR:
224							message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
225							mXmppConnectionService.updateMessage(message);
226							break;
227					}
228				} catch (final IOException e) {
229					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
230					mXmppConnectionService.updateMessage(message);
231				}
232			}
233		}
234		notifyIfPending(message);
235	}
236
237	private synchronized void notifyIfPending(Message message) {
238		if (pendingNotifications.remove(message)) {
239			mXmppConnectionService.getNotificationService().push(message);
240		}
241	}
242
243	private void storePendingIntent(PendingIntent pendingIntent) {
244		this.pendingIntent = pendingIntent;
245		mXmppConnectionService.updateConversationUi();
246	}
247
248	public synchronized boolean hasPendingIntent(Conversation conversation) {
249		if (pendingIntent == null) {
250			return false;
251		} else {
252			for (Message message : messages) {
253				if (message.getConversation() == conversation) {
254					return true;
255				}
256			}
257			return false;
258		}
259	}
260
261	public PendingIntent getPendingIntent() {
262		return pendingIntent;
263	}
264
265	public boolean isConnected() {
266		return getOpenPgpApi() != null;
267	}
268}