]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/util/Utils.java
Update libsignal-service
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / util / Utils.java
1 package org.asamk.signal.manager.util;
2
3 import org.asamk.signal.manager.api.Pair;
4 import org.signal.libsignal.protocol.IdentityKey;
5 import org.signal.libsignal.protocol.fingerprint.Fingerprint;
6 import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9 import org.whispersystems.signalservice.api.NetworkResult;
10 import org.whispersystems.signalservice.api.NetworkResultUtil;
11 import org.whispersystems.signalservice.api.push.ServiceId;
12 import org.whispersystems.signalservice.api.util.StreamDetails;
13
14 import java.io.ByteArrayInputStream;
15 import java.io.File;
16 import java.io.FileInputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.net.Proxy;
20 import java.net.ProxySelector;
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.net.URLDecoder;
24 import java.nio.charset.StandardCharsets;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.Spliterator;
31 import java.util.Spliterators;
32 import java.util.function.BiFunction;
33 import java.util.function.Consumer;
34 import java.util.function.Function;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37 import java.util.stream.StreamSupport;
38
39 import okio.ByteString;
40
41 public class Utils {
42
43 private static final Logger logger = LoggerFactory.getLogger(Utils.class);
44
45 public static Pair<StreamDetails, Optional<String>> createStreamDetailsFromDataURI(final String dataURI) {
46 final DataURI uri = DataURI.of(dataURI);
47
48 return new Pair<>(new StreamDetails(new ByteArrayInputStream(uri.data()), uri.mediaType(), uri.data().length),
49 Optional.ofNullable(uri.parameter().get("filename")));
50 }
51
52 public static StreamDetails createStreamDetailsFromFile(final File file) throws IOException {
53 final InputStream stream = new FileInputStream(file);
54 final var size = file.length();
55 final var mime = MimeUtils.getFileMimeType(file).orElse(MimeUtils.OCTET_STREAM);
56 return new StreamDetails(stream, mime, size);
57 }
58
59 public static Pair<StreamDetails, Optional<String>> createStreamDetails(final String value) throws IOException {
60 try {
61 return createStreamDetailsFromDataURI(value);
62 } catch (final IllegalArgumentException e) {
63 final File f = new File(value);
64
65 return new Pair<>(createStreamDetailsFromFile(f), Optional.of(f.getName()));
66 }
67 }
68
69 public static Fingerprint computeSafetyNumberForNumber(
70 String ownNumber,
71 IdentityKey ownIdentityKey,
72 String theirNumber,
73 IdentityKey theirIdentityKey
74 ) {
75 // Version 1: E164 user
76 final var version = 1;
77 final var ownId = ownNumber.getBytes(StandardCharsets.UTF_8);
78 final var theirId = theirNumber.getBytes(StandardCharsets.UTF_8);
79
80 return getFingerprint(version, ownId, ownIdentityKey, theirId, theirIdentityKey);
81 }
82
83 public static Fingerprint computeSafetyNumberForUuid(
84 ServiceId ownServiceId,
85 IdentityKey ownIdentityKey,
86 ServiceId theirServiceId,
87 IdentityKey theirIdentityKey
88 ) {
89 // Version 2: UUID user
90 final var version = 2;
91 final var ownId = ownServiceId.toByteArray();
92 final var theirId = theirServiceId.toByteArray();
93
94 return getFingerprint(version, ownId, ownIdentityKey, theirId, theirIdentityKey);
95 }
96
97 private static Fingerprint getFingerprint(
98 final int version,
99 final byte[] ownId,
100 final IdentityKey ownIdentityKey,
101 final byte[] theirId,
102 final IdentityKey theirIdentityKey
103 ) {
104 return new NumericFingerprintGenerator(5200).createFor(version,
105 ownId,
106 ownIdentityKey,
107 theirId,
108 theirIdentityKey);
109 }
110
111 public static Locale getDefaultLocale(Locale fallback) {
112 final var locale = Locale.getDefault();
113 if (locale == null) {
114 logger.debug("No default locale found, using fallback: {}", fallback);
115 return fallback;
116 }
117 final var localeString = locale.getLanguage() + "-" + locale.getCountry();
118 try {
119 Locale.LanguageRange.parse(localeString);
120 } catch (IllegalArgumentException e) {
121 logger.debug("Invalid locale '{}', using fallback: {}", locale, fallback);
122 return fallback;
123 }
124
125 logger.trace("Using default locale: {} ({})", locale, localeString);
126 return locale;
127 }
128
129 public static <L, R, T> Stream<T> zip(Stream<L> leftStream, Stream<R> rightStream, BiFunction<L, R, T> combiner) {
130 Spliterator<L> lefts = leftStream.spliterator();
131 Spliterator<R> rights = rightStream.spliterator();
132 return StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.min(lefts.estimateSize(),
133 rights.estimateSize()), lefts.characteristics() & rights.characteristics()) {
134 @Override
135 public boolean tryAdvance(Consumer<? super T> action) {
136 return lefts.tryAdvance(left -> rights.tryAdvance(right -> action.accept(combiner.apply(left, right))));
137 }
138 }, leftStream.isParallel() || rightStream.isParallel());
139 }
140
141 public static <OK, NK, V> Map<NK, V> mapKeys(Map<OK, V> map, Function<OK, NK> keyMapper) {
142 return map.entrySet().stream().collect(Collectors.toMap(e -> keyMapper.apply(e.getKey()), Map.Entry::getValue));
143 }
144
145 public static Map<String, String> getQueryMap(String query) {
146 var params = query.split("&");
147 var map = new HashMap<String, String>();
148 for (var param : params) {
149 final var paramParts = param.split("=");
150 var name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
151 var value = paramParts.length == 1 ? null : URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
152 map.put(name, value);
153 }
154 return map;
155 }
156
157 public static <T> T handleResponseException(final NetworkResult<T> response) throws IOException {
158 return NetworkResultUtil.toBasicLegacy(response);
159 }
160
161 public static ByteString firstNonEmpty(ByteString... strings) {
162 for (final var s : strings) {
163 if (s.size() > 0) {
164 return s;
165 }
166 }
167 return ByteString.EMPTY;
168 }
169
170 @SafeVarargs
171 public static <T> List<T> firstNonEmpty(List<T>... values) {
172 for (final var s : values) {
173 if (!s.isEmpty()) {
174 return s;
175 }
176 }
177 return List.of();
178 }
179
180 public static String firstNonEmpty(String... strings) {
181 for (final var s : strings) {
182 if (!s.isEmpty()) {
183 return s;
184 }
185 }
186 return "";
187 }
188
189 @SafeVarargs
190 public static <T> T firstNonNull(T... values) {
191 for (final var v : values) {
192 if (v != null) {
193 return v;
194 }
195 }
196 return null;
197 }
198
199 public static String nullIfEmpty(String string) {
200 return string == null || string.isEmpty() ? null : string;
201 }
202
203 public static Proxy getHttpsProxy() {
204 final URI uri;
205 try {
206 uri = new URI("https://example");
207 } catch (URISyntaxException e) {
208 throw new RuntimeException(e);
209 }
210 final var proxies = ProxySelector.getDefault().select(uri);
211 if (proxies.isEmpty()) {
212 return Proxy.NO_PROXY;
213 } else {
214 return proxies.getFirst();
215 }
216 }
217 }