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}