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