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.List;
18
19import eu.siacs.conversations.entities.Conversation;
20import eu.siacs.conversations.entities.DownloadableFile;
21import eu.siacs.conversations.entities.Message;
22import eu.siacs.conversations.http.HttpConnectionManager;
23import eu.siacs.conversations.services.XmppConnectionService;
24
25public class PgpDecryptionService {
26
27 private final XmppConnectionService mXmppConnectionService;
28 private OpenPgpApi openPgpApi = null;
29
30 protected final ArrayDeque<Message> messages = new ArrayDeque();
31 Message currentMessage;
32 private PendingIntent pendingIntent;
33
34
35 public PgpDecryptionService(XmppConnectionService service) {
36 this.mXmppConnectionService = service;
37 }
38
39 public synchronized void decrypt(final Message message) {
40 messages.add(message);
41 continueDecryption();
42 }
43
44 public synchronized void decrypt(final List<Message> list) {
45 for(Message message : list) {
46 if (message.getEncryption() == Message.ENCRYPTION_PGP) {
47 messages.add(message);
48 }
49 }
50 continueDecryption();
51 }
52
53 public synchronized void discard(List<Message> discards) {
54 this.messages.removeAll(discards);
55 }
56
57 protected synchronized void decryptNext() {
58 if (pendingIntent == null
59 && getOpenPgpApi() != null
60 && (currentMessage = messages.poll()) != null) {
61 new Thread(new Runnable() {
62 @Override
63 public void run() {
64 executeApi(currentMessage);
65 decryptNext();
66 }
67 }).start();
68 }
69 }
70
71 public synchronized void continueDecryption(boolean resetPending) {
72 if (resetPending) {
73 this.pendingIntent = null;
74 }
75 continueDecryption();
76 }
77
78 public synchronized void continueDecryption() {
79 if (currentMessage == null) {
80 decryptNext();
81 }
82 }
83
84 private synchronized OpenPgpApi getOpenPgpApi() {
85 if (openPgpApi == null) {
86 this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
87 }
88 return this.openPgpApi;
89 }
90
91 private void executeApi(Message message) {
92 Intent params = new Intent();
93 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
94 if (message.getType() == Message.TYPE_TEXT) {
95 InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
96 final OutputStream os = new ByteArrayOutputStream();
97 Intent result = getOpenPgpApi().executeApi(params, is, os);
98 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
99 case OpenPgpApi.RESULT_CODE_SUCCESS:
100 try {
101 os.flush();
102 message.setBody(os.toString());
103 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
104 final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
105 if (message.trusted()
106 && message.treatAsDownloadable() != Message.Decision.NEVER
107 && manager.getAutoAcceptFileSize() > 0) {
108 manager.createNewDownloadConnection(message);
109 }
110 } catch (IOException e) {
111 message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
112 }
113 mXmppConnectionService.updateMessage(message);
114 break;
115 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
116 messages.addFirst(message);
117 currentMessage = null;
118 storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
119 break;
120 case OpenPgpApi.RESULT_CODE_ERROR:
121 message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
122 mXmppConnectionService.updateMessage(message);
123 break;
124 }
125 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
126 try {
127 final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
128 final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
129 outputFile.getParentFile().mkdirs();
130 outputFile.createNewFile();
131 InputStream is = new FileInputStream(inputFile);
132 OutputStream os = new FileOutputStream(outputFile);
133 Intent result = getOpenPgpApi().executeApi(params, is, os);
134 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
135 case OpenPgpApi.RESULT_CODE_SUCCESS:
136 URL url = message.getFileParams().url;
137 mXmppConnectionService.getFileBackend().updateFileParams(message,url);
138 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
139 inputFile.delete();
140 mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
141 mXmppConnectionService.updateMessage(message);
142 break;
143 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
144 messages.addFirst(message);
145 currentMessage = null;
146 storePendingIntent((PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
147 break;
148 case OpenPgpApi.RESULT_CODE_ERROR:
149 message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
150 mXmppConnectionService.updateMessage(message);
151 break;
152 }
153 } catch (final IOException e) {
154 message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
155 mXmppConnectionService.updateMessage(message);
156 }
157 }
158 }
159
160 private void storePendingIntent(PendingIntent pendingIntent) {
161 this.pendingIntent = pendingIntent;
162 mXmppConnectionService.updateConversationUi();
163 }
164
165 public synchronized boolean hasPendingIntent(Conversation conversation) {
166 if (pendingIntent == null) {
167 return false;
168 } else {
169 for(Message message : messages) {
170 if (message.getConversation() == conversation) {
171 return true;
172 }
173 }
174 return false;
175 }
176 }
177
178 public PendingIntent getPendingIntent() {
179 return pendingIntent;
180 }
181
182 public boolean isConnected() {
183 return getOpenPgpApi() != null;
184 }
185}