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