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