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