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