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}