]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/manager/Utils.java
Add listContacts subcommand (#248)
[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.signalservice.internal.util.Base64;
20
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.URLDecoder;
33 import java.net.URLEncoder;
34 import java.nio.file.Files;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42
43 import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
44
45 class Utils {
46
47 static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
48 List<SignalServiceAttachment> SignalServiceAttachments = null;
49 if (attachments != null) {
50 SignalServiceAttachments = new ArrayList<>(attachments.size());
51 for (String attachment : attachments) {
52 try {
53 SignalServiceAttachments.add(createAttachment(new File(attachment)));
54 } catch (IOException e) {
55 throw new AttachmentInvalidException(attachment, e);
56 }
57 }
58 }
59 return SignalServiceAttachments;
60 }
61
62 static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
63 InputStream attachmentStream = new FileInputStream(attachmentFile);
64 final long attachmentSize = attachmentFile.length();
65 String mime = Files.probeContentType(attachmentFile.toPath());
66 if (mime == null) {
67 mime = "application/octet-stream";
68 }
69 // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
70 Optional<byte[]> preview = Optional.absent();
71 Optional<String> caption = Optional.absent();
72 Optional<String> blurHash = Optional.absent();
73 return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, blurHash, null);
74 }
75
76 static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
77 InputStream stream = new FileInputStream(file);
78 final long size = file.length();
79 String mime = Files.probeContentType(file.toPath());
80 if (mime == null) {
81 mime = "application/octet-stream";
82 }
83 return new StreamDetails(stream, mime, size);
84 }
85
86 static CertificateValidator getCertificateValidator() {
87 try {
88 ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
89 return new CertificateValidator(unidentifiedSenderTrustRoot);
90 } catch (InvalidKeyException | IOException e) {
91 throw new AssertionError(e);
92 }
93 }
94
95 private static Map<String, String> getQueryMap(String query) {
96 String[] params = query.split("&");
97 Map<String, String> map = new HashMap<>();
98 for (String param : params) {
99 String name = null;
100 final String[] paramParts = param.split("=");
101 try {
102 name = URLDecoder.decode(paramParts[0], "utf-8");
103 } catch (UnsupportedEncodingException e) {
104 // Impossible
105 }
106 String value = null;
107 try {
108 value = URLDecoder.decode(paramParts[1], "utf-8");
109 } catch (UnsupportedEncodingException e) {
110 // Impossible
111 }
112 map.put(name, value);
113 }
114 return map;
115 }
116
117 static String createDeviceLinkUri(DeviceLinkInfo info) {
118 try {
119 return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
120 } catch (UnsupportedEncodingException e) {
121 // Shouldn't happen
122 return null;
123 }
124 }
125
126 static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
127 Map<String, String> query = getQueryMap(linkUri.getRawQuery());
128 String deviceIdentifier = query.get("uuid");
129 String publicKeyEncoded = query.get("pub_key");
130
131 if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
132 throw new RuntimeException("Invalid device link uri");
133 }
134
135 ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
136
137 return new DeviceLinkInfo(deviceIdentifier, deviceKey);
138 }
139
140 static Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients, String localNumber) {
141 Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
142 for (String recipient : recipients) {
143 try {
144 recipientsTS.add(getPushAddress(recipient, localNumber));
145 } catch (InvalidNumberException e) {
146 System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
147 System.err.println("Aborting sending.");
148 return null;
149 }
150 }
151 return recipientsTS;
152 }
153
154 static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
155 return PhoneNumberFormatter.formatNumber(number, localNumber);
156 }
157
158 private static SignalServiceAddress getPushAddress(String number, String localNumber) throws InvalidNumberException {
159 String e164number = canonicalizeNumber(number, localNumber);
160 return new SignalServiceAddress(e164number);
161 }
162
163 static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
164 try (FileInputStream f = new FileInputStream(file)) {
165 DataInputStream in = new DataInputStream(f);
166 int version = in.readInt();
167 if (version > 2) {
168 return null;
169 }
170 int type = in.readInt();
171 String source = in.readUTF();
172 int sourceDevice = in.readInt();
173 if (version == 1) {
174 // read legacy relay field
175 in.readUTF();
176 }
177 long timestamp = in.readLong();
178 byte[] content = null;
179 int contentLen = in.readInt();
180 if (contentLen > 0) {
181 content = new byte[contentLen];
182 in.readFully(content);
183 }
184 byte[] legacyMessage = null;
185 int legacyMessageLen = in.readInt();
186 if (legacyMessageLen > 0) {
187 legacyMessage = new byte[legacyMessageLen];
188 in.readFully(legacyMessage);
189 }
190 long serverTimestamp = 0;
191 String uuid = null;
192 if (version == 2) {
193 serverTimestamp = in.readLong();
194 uuid = in.readUTF();
195 if ("".equals(uuid)) {
196 uuid = null;
197 }
198 }
199 return new SignalServiceEnvelope(type, source, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
200 }
201 }
202
203 static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
204 try (FileOutputStream f = new FileOutputStream(file)) {
205 try (DataOutputStream out = new DataOutputStream(f)) {
206 out.writeInt(2); // version
207 out.writeInt(envelope.getType());
208 out.writeUTF(envelope.getSource());
209 out.writeInt(envelope.getSourceDevice());
210 out.writeLong(envelope.getTimestamp());
211 if (envelope.hasContent()) {
212 out.writeInt(envelope.getContent().length);
213 out.write(envelope.getContent());
214 } else {
215 out.writeInt(0);
216 }
217 if (envelope.hasLegacyMessage()) {
218 out.writeInt(envelope.getLegacyMessage().length);
219 out.write(envelope.getLegacyMessage());
220 } else {
221 out.writeInt(0);
222 }
223 out.writeLong(envelope.getServerTimestamp());
224 String uuid = envelope.getUuid();
225 out.writeUTF(uuid == null ? "" : uuid);
226 }
227 }
228 }
229
230 static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
231 InputStream input = stream.getInputStream();
232
233 try (OutputStream output = new FileOutputStream(outputFile)) {
234 byte[] buffer = new byte[4096];
235 int read;
236
237 while ((read = input.read(buffer)) != -1) {
238 output.write(buffer, 0, read);
239 }
240 } catch (FileNotFoundException e) {
241 e.printStackTrace();
242 return null;
243 }
244 return outputFile;
245 }
246
247 static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) {
248 Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(ownUsername, ownIdentityKey, theirUsername, theirIdentityKey);
249 return fingerprint.getDisplayableFingerprint().getDisplayText();
250 }
251
252 static class DeviceLinkInfo {
253
254 final String deviceIdentifier;
255 final ECPublicKey deviceKey;
256
257 DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
258 this.deviceIdentifier = deviceIdentifier;
259 this.deviceKey = deviceKey;
260 }
261 }
262 }