]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/Utils.java
Move Exception classes to corresponding package
[signal-cli] / src / main / java / org / asamk / signal / manager / Utils.java
1 package org.asamk.signal.manager;
2
3 import org.signal.libsignal.metadata.certificate.CertificateValidator;
4 import org.whispersystems.libsignal.IdentityKey;
5 import org.whispersystems.libsignal.InvalidKeyException;
6 import org.whispersystems.libsignal.ecc.Curve;
7 import org.whispersystems.libsignal.ecc.ECPublicKey;
8 import org.whispersystems.libsignal.fingerprint.Fingerprint;
9 import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
10 import org.whispersystems.libsignal.util.guava.Optional;
11 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
12 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
13 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
14 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
15 import org.whispersystems.signalservice.api.util.StreamDetails;
16 import org.whispersystems.signalservice.api.util.UuidUtil;
17 import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
18 import org.whispersystems.util.Base64;
19
20 import java.io.BufferedInputStream;
21 import java.io.DataInputStream;
22 import java.io.DataOutputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.io.UnsupportedEncodingException;
31 import java.net.URI;
32 import java.net.URLConnection;
33 import java.net.URLDecoder;
34 import java.net.URLEncoder;
35 import java.nio.file.Files;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.UUID;
41
42 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
43
44 class Utils {
45
46 static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
47 List<SignalServiceAttachment> signalServiceAttachments = null;
48 if (attachments != null) {
49 signalServiceAttachments = new ArrayList<>(attachments.size());
50 for (String attachment : attachments) {
51 try {
52 signalServiceAttachments.add(createAttachment(new File(attachment)));
53 } catch (IOException e) {
54 throw new AttachmentInvalidException(attachment, e);
55 }
56 }
57 }
58 return signalServiceAttachments;
59 }
60
61 private static String getFileMimeType(File file) throws IOException {
62 String mime = Files.probeContentType(file.toPath());
63 if (mime == null) {
64 try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
65 mime = URLConnection.guessContentTypeFromStream(bufferedStream);
66 }
67 }
68 if (mime == null) {
69 mime = "application/octet-stream";
70 }
71 return mime;
72 }
73
74 static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
75 InputStream attachmentStream = new FileInputStream(attachmentFile);
76 final long attachmentSize = attachmentFile.length();
77 final String mime = getFileMimeType(attachmentFile);
78 // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
79 final long uploadTimestamp = System.currentTimeMillis();
80 Optional<byte[]> preview = Optional.absent();
81 Optional<String> caption = Optional.absent();
82 Optional<String> blurHash = Optional.absent();
83 final Optional<ResumableUploadSpec> resumableUploadSpec = Optional.absent();
84 return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, uploadTimestamp, caption, blurHash, null, null, resumableUploadSpec);
85 }
86
87 static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
88 InputStream stream = new FileInputStream(file);
89 final long size = file.length();
90 String mime = Files.probeContentType(file.toPath());
91 if (mime == null) {
92 mime = "application/octet-stream";
93 }
94 return new StreamDetails(stream, mime, size);
95 }
96
97 static CertificateValidator getCertificateValidator() {
98 try {
99 ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(ServiceConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
100 return new CertificateValidator(unidentifiedSenderTrustRoot);
101 } catch (InvalidKeyException | IOException e) {
102 throw new AssertionError(e);
103 }
104 }
105
106 private static Map<String, String> getQueryMap(String query) {
107 String[] params = query.split("&");
108 Map<String, String> map = new HashMap<>();
109 for (String param : params) {
110 String name = null;
111 final String[] paramParts = param.split("=");
112 try {
113 name = URLDecoder.decode(paramParts[0], "utf-8");
114 } catch (UnsupportedEncodingException e) {
115 // Impossible
116 }
117 String value = null;
118 try {
119 value = URLDecoder.decode(paramParts[1], "utf-8");
120 } catch (UnsupportedEncodingException e) {
121 // Impossible
122 }
123 map.put(name, value);
124 }
125 return map;
126 }
127
128 static String createDeviceLinkUri(DeviceLinkInfo info) {
129 try {
130 return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
131 } catch (UnsupportedEncodingException e) {
132 // Shouldn't happen
133 return null;
134 }
135 }
136
137 static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
138 Map<String, String> query = getQueryMap(linkUri.getRawQuery());
139 String deviceIdentifier = query.get("uuid");
140 String publicKeyEncoded = query.get("pub_key");
141
142 if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
143 throw new RuntimeException("Invalid device link uri");
144 }
145
146 ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
147
148 return new DeviceLinkInfo(deviceIdentifier, deviceKey);
149 }
150
151 static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
152 try (FileInputStream f = new FileInputStream(file)) {
153 DataInputStream in = new DataInputStream(f);
154 int version = in.readInt();
155 if (version > 3) {
156 return null;
157 }
158 int type = in.readInt();
159 String source = in.readUTF();
160 UUID sourceUuid = null;
161 if (version >= 3) {
162 sourceUuid = UuidUtil.parseOrNull(in.readUTF());
163 }
164 int sourceDevice = in.readInt();
165 if (version == 1) {
166 // read legacy relay field
167 in.readUTF();
168 }
169 long timestamp = in.readLong();
170 byte[] content = null;
171 int contentLen = in.readInt();
172 if (contentLen > 0) {
173 content = new byte[contentLen];
174 in.readFully(content);
175 }
176 byte[] legacyMessage = null;
177 int legacyMessageLen = in.readInt();
178 if (legacyMessageLen > 0) {
179 legacyMessage = new byte[legacyMessageLen];
180 in.readFully(legacyMessage);
181 }
182 long serverTimestamp = 0;
183 String uuid = null;
184 if (version == 2) {
185 serverTimestamp = in.readLong();
186 uuid = in.readUTF();
187 if ("".equals(uuid)) {
188 uuid = null;
189 }
190 }
191 Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
192 ? Optional.absent()
193 : Optional.of(new SignalServiceAddress(sourceUuid, source));
194 return new SignalServiceEnvelope(type, addressOptional, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
195 }
196 }
197
198 static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
199 try (FileOutputStream f = new FileOutputStream(file)) {
200 try (DataOutputStream out = new DataOutputStream(f)) {
201 out.writeInt(3); // version
202 out.writeInt(envelope.getType());
203 out.writeUTF(envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "");
204 out.writeUTF(envelope.getSourceUuid().isPresent() ? envelope.getSourceUuid().get() : "");
205 out.writeInt(envelope.getSourceDevice());
206 out.writeLong(envelope.getTimestamp());
207 if (envelope.hasContent()) {
208 out.writeInt(envelope.getContent().length);
209 out.write(envelope.getContent());
210 } else {
211 out.writeInt(0);
212 }
213 if (envelope.hasLegacyMessage()) {
214 out.writeInt(envelope.getLegacyMessage().length);
215 out.write(envelope.getLegacyMessage());
216 } else {
217 out.writeInt(0);
218 }
219 out.writeLong(envelope.getServerTimestamp());
220 String uuid = envelope.getUuid();
221 out.writeUTF(uuid == null ? "" : uuid);
222 }
223 }
224 }
225
226 static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
227 InputStream input = stream.getInputStream();
228
229 try (OutputStream output = new FileOutputStream(outputFile)) {
230 byte[] buffer = new byte[4096];
231 int read;
232
233 while ((read = input.read(buffer)) != -1) {
234 output.write(buffer, 0, read);
235 }
236 } catch (FileNotFoundException e) {
237 e.printStackTrace();
238 return null;
239 }
240 return outputFile;
241 }
242
243 static String computeSafetyNumber(SignalServiceAddress ownAddress, IdentityKey ownIdentityKey, SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
244 int version;
245 byte[] ownId;
246 byte[] theirId;
247
248 if (ServiceConfig.capabilities.isUuid()
249 && ownAddress.getUuid().isPresent() && theirAddress.getUuid().isPresent()) {
250 // Version 2: UUID user
251 version = 2;
252 ownId = UuidUtil.toByteArray(ownAddress.getUuid().get());
253 theirId = UuidUtil.toByteArray(theirAddress.getUuid().get());
254 } else {
255 // Version 1: E164 user
256 version = 1;
257 if (!ownAddress.getNumber().isPresent() || !theirAddress.getNumber().isPresent()) {
258 return "INVALID ID";
259 }
260 ownId = ownAddress.getNumber().get().getBytes();
261 theirId = theirAddress.getNumber().get().getBytes();
262 }
263
264 Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(version, ownId, ownIdentityKey, theirId, theirIdentityKey);
265 return fingerprint.getDisplayableFingerprint().getDisplayText();
266 }
267
268 static class DeviceLinkInfo {
269
270 final String deviceIdentifier;
271 final ECPublicKey deviceKey;
272
273 DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
274 this.deviceIdentifier = deviceIdentifier;
275 this.deviceKey = deviceKey;
276 }
277 }
278 }