]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/Utils.java
Use SignalServiceAddress in more places
[signal-cli] / src / main / java / org / asamk / signal / manager / Utils.java
1 package org.asamk.signal.manager;
2
3 import org.asamk.signal.AttachmentInvalidException;
4 import org.signal.libsignal.metadata.certificate.CertificateValidator;
5 import org.whispersystems.libsignal.IdentityKey;
6 import org.whispersystems.libsignal.InvalidKeyException;
7 import org.whispersystems.libsignal.ecc.Curve;
8 import org.whispersystems.libsignal.ecc.ECPublicKey;
9 import org.whispersystems.libsignal.fingerprint.Fingerprint;
10 import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
11 import org.whispersystems.libsignal.util.guava.Optional;
12 import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
13 import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
14 import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
15 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
16 import org.whispersystems.signalservice.api.util.InvalidNumberException;
17 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
18 import org.whispersystems.signalservice.api.util.StreamDetails;
19 import org.whispersystems.util.Base64;
20
21 import java.io.BufferedInputStream;
22 import java.io.DataInputStream;
23 import java.io.DataOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.io.UnsupportedEncodingException;
32 import java.net.URI;
33 import java.net.URLConnection;
34 import java.net.URLDecoder;
35 import java.net.URLEncoder;
36 import java.nio.file.Files;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
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 Optional<byte[]> preview = Optional.absent();
80 Optional<String> caption = Optional.absent();
81 Optional<String> blurHash = Optional.absent();
82 return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, blurHash, null, null);
83 }
84
85 static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
86 InputStream stream = new FileInputStream(file);
87 final long size = file.length();
88 String mime = Files.probeContentType(file.toPath());
89 if (mime == null) {
90 mime = "application/octet-stream";
91 }
92 return new StreamDetails(stream, mime, size);
93 }
94
95 static CertificateValidator getCertificateValidator() {
96 try {
97 ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
98 return new CertificateValidator(unidentifiedSenderTrustRoot);
99 } catch (InvalidKeyException | IOException e) {
100 throw new AssertionError(e);
101 }
102 }
103
104 private static Map<String, String> getQueryMap(String query) {
105 String[] params = query.split("&");
106 Map<String, String> map = new HashMap<>();
107 for (String param : params) {
108 String name = null;
109 final String[] paramParts = param.split("=");
110 try {
111 name = URLDecoder.decode(paramParts[0], "utf-8");
112 } catch (UnsupportedEncodingException e) {
113 // Impossible
114 }
115 String value = null;
116 try {
117 value = URLDecoder.decode(paramParts[1], "utf-8");
118 } catch (UnsupportedEncodingException e) {
119 // Impossible
120 }
121 map.put(name, value);
122 }
123 return map;
124 }
125
126 static String createDeviceLinkUri(DeviceLinkInfo info) {
127 try {
128 return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
129 } catch (UnsupportedEncodingException e) {
130 // Shouldn't happen
131 return null;
132 }
133 }
134
135 static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
136 Map<String, String> query = getQueryMap(linkUri.getRawQuery());
137 String deviceIdentifier = query.get("uuid");
138 String publicKeyEncoded = query.get("pub_key");
139
140 if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
141 throw new RuntimeException("Invalid device link uri");
142 }
143
144 ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
145
146 return new DeviceLinkInfo(deviceIdentifier, deviceKey);
147 }
148
149 static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
150 return PhoneNumberFormatter.formatNumber(number, localNumber);
151 }
152
153 static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
154 try (FileInputStream f = new FileInputStream(file)) {
155 DataInputStream in = new DataInputStream(f);
156 int version = in.readInt();
157 if (version > 2) {
158 return null;
159 }
160 int type = in.readInt();
161 String source = in.readUTF();
162 int sourceDevice = in.readInt();
163 if (version == 1) {
164 // read legacy relay field
165 in.readUTF();
166 }
167 long timestamp = in.readLong();
168 byte[] content = null;
169 int contentLen = in.readInt();
170 if (contentLen > 0) {
171 content = new byte[contentLen];
172 in.readFully(content);
173 }
174 byte[] legacyMessage = null;
175 int legacyMessageLen = in.readInt();
176 if (legacyMessageLen > 0) {
177 legacyMessage = new byte[legacyMessageLen];
178 in.readFully(legacyMessage);
179 }
180 long serverTimestamp = 0;
181 String uuid = null;
182 if (version == 2) {
183 serverTimestamp = in.readLong();
184 uuid = in.readUTF();
185 if ("".equals(uuid)) {
186 uuid = null;
187 }
188 }
189 return new SignalServiceEnvelope(type, Optional.of(new SignalServiceAddress(null, source)), sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
190 }
191 }
192
193 static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
194 try (FileOutputStream f = new FileOutputStream(file)) {
195 try (DataOutputStream out = new DataOutputStream(f)) {
196 out.writeInt(2); // version
197 out.writeInt(envelope.getType());
198 out.writeUTF(envelope.getSourceE164().get());
199 out.writeInt(envelope.getSourceDevice());
200 out.writeLong(envelope.getTimestamp());
201 if (envelope.hasContent()) {
202 out.writeInt(envelope.getContent().length);
203 out.write(envelope.getContent());
204 } else {
205 out.writeInt(0);
206 }
207 if (envelope.hasLegacyMessage()) {
208 out.writeInt(envelope.getLegacyMessage().length);
209 out.write(envelope.getLegacyMessage());
210 } else {
211 out.writeInt(0);
212 }
213 out.writeLong(envelope.getServerTimestamp());
214 String uuid = envelope.getUuid();
215 out.writeUTF(uuid == null ? "" : uuid);
216 }
217 }
218 }
219
220 static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
221 InputStream input = stream.getInputStream();
222
223 try (OutputStream output = new FileOutputStream(outputFile)) {
224 byte[] buffer = new byte[4096];
225 int read;
226
227 while ((read = input.read(buffer)) != -1) {
228 output.write(buffer, 0, read);
229 }
230 } catch (FileNotFoundException e) {
231 e.printStackTrace();
232 return null;
233 }
234 return outputFile;
235 }
236
237 static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) {
238 // Version 1: E164 user
239 // Version 2: UUID user
240 Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(1, ownUsername.getBytes(), ownIdentityKey, theirUsername.getBytes(), theirIdentityKey);
241 return fingerprint.getDisplayableFingerprint().getDisplayText();
242 }
243
244 static class DeviceLinkInfo {
245
246 final String deviceIdentifier;
247 final ECPublicKey deviceKey;
248
249 DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
250 this.deviceIdentifier = deviceIdentifier;
251 this.deviceKey = deviceKey;
252 }
253 }
254 }