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