]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/util/Utils.java
Implement username links
[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.push.ServiceId;
10 import org.whispersystems.signalservice.api.util.StreamDetails;
11 import org.whispersystems.signalservice.internal.ServiceResponse;
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, IdentityKey ownIdentityKey, String theirNumber, IdentityKey theirIdentityKey
63 ) {
64 // Version 1: E164 user
65 final var version = 1;
66 final var ownId = ownNumber.getBytes(StandardCharsets.UTF_8);
67 final var theirId = theirNumber.getBytes(StandardCharsets.UTF_8);
68
69 return getFingerprint(version, ownId, ownIdentityKey, theirId, theirIdentityKey);
70 }
71
72 public static Fingerprint computeSafetyNumberForUuid(
73 ServiceId ownServiceId, IdentityKey ownIdentityKey, ServiceId theirServiceId, IdentityKey theirIdentityKey
74 ) {
75 // Version 2: UUID user
76 final var version = 2;
77 final var ownId = ownServiceId.toByteArray();
78 final var theirId = theirServiceId.toByteArray();
79
80 return getFingerprint(version, ownId, ownIdentityKey, theirId, theirIdentityKey);
81 }
82
83 private static Fingerprint getFingerprint(
84 final int version,
85 final byte[] ownId,
86 final IdentityKey ownIdentityKey,
87 final byte[] theirId,
88 final IdentityKey theirIdentityKey
89 ) {
90 return new NumericFingerprintGenerator(5200).createFor(version,
91 ownId,
92 ownIdentityKey,
93 theirId,
94 theirIdentityKey);
95 }
96
97 public static Locale getDefaultLocale(Locale fallback) {
98 final var locale = Locale.getDefault();
99 if (locale == null) {
100 logger.debug("No default locale found, using fallback: {}", fallback);
101 return fallback;
102 }
103 final var localeString = locale.getLanguage() + "-" + locale.getCountry();
104 try {
105 Locale.LanguageRange.parse(localeString);
106 } catch (IllegalArgumentException e) {
107 logger.debug("Invalid locale '{}', using fallback: {}", locale, fallback);
108 return fallback;
109 }
110
111 logger.trace("Using default locale: {} ({})", locale, localeString);
112 return locale;
113 }
114
115 public static <L, R, T> Stream<T> zip(Stream<L> leftStream, Stream<R> rightStream, BiFunction<L, R, T> combiner) {
116 Spliterator<L> lefts = leftStream.spliterator();
117 Spliterator<R> rights = rightStream.spliterator();
118 return StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.min(lefts.estimateSize(),
119 rights.estimateSize()), lefts.characteristics() & rights.characteristics()) {
120 @Override
121 public boolean tryAdvance(Consumer<? super T> action) {
122 return lefts.tryAdvance(left -> rights.tryAdvance(right -> action.accept(combiner.apply(left, right))));
123 }
124 }, leftStream.isParallel() || rightStream.isParallel());
125 }
126
127 public static <OK, NK, V> Map<NK, V> mapKeys(Map<OK, V> map, Function<OK, NK> keyMapper) {
128 return map.entrySet().stream().collect(Collectors.toMap(e -> keyMapper.apply(e.getKey()), Map.Entry::getValue));
129 }
130
131 public static Map<String, String> getQueryMap(String query) {
132 var params = query.split("&");
133 var map = new HashMap<String, String>();
134 for (var param : params) {
135 final var paramParts = param.split("=");
136 var name = URLDecoder.decode(paramParts[0], StandardCharsets.UTF_8);
137 var value = paramParts.length == 1 ? null : URLDecoder.decode(paramParts[1], StandardCharsets.UTF_8);
138 map.put(name, value);
139 }
140 return map;
141 }
142
143 public static <T> T handleResponseException(final ServiceResponse<T> response) throws IOException {
144 final var throwableOptional = response.getExecutionError().or(response::getApplicationError);
145 if (throwableOptional.isPresent()) {
146 if (throwableOptional.get() instanceof IOException) {
147 throw (IOException) throwableOptional.get();
148 } else {
149 throw new IOException(throwableOptional.get());
150 }
151 }
152 return response.getResult().orElse(null);
153 }
154 }