1 package org
.asamk
.signal
.manager
.util
;
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
.SignalServiceAddress
;
10 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
12 import java
.io
.BufferedInputStream
;
13 import java
.io
.ByteArrayInputStream
;
15 import java
.io
.FileInputStream
;
16 import java
.io
.IOException
;
17 import java
.io
.InputStream
;
18 import java
.net
.URLConnection
;
19 import java
.net
.URLDecoder
;
20 import java
.nio
.charset
.StandardCharsets
;
21 import java
.nio
.file
.Files
;
22 import java
.util
.Base64
;
23 import java
.util
.HashMap
;
24 import java
.util
.Locale
;
26 import java
.util
.Optional
;
27 import java
.util
.Spliterator
;
28 import java
.util
.Spliterators
;
29 import java
.util
.function
.BiFunction
;
30 import java
.util
.function
.Consumer
;
31 import java
.util
.stream
.Stream
;
32 import java
.util
.stream
.StreamSupport
;
36 private final static Logger logger
= LoggerFactory
.getLogger(Utils
.class);
38 public static String
getFileMimeType(final File file
, final String defaultMimeType
) throws IOException
{
39 var mime
= Files
.probeContentType(file
.toPath());
41 try (final InputStream bufferedStream
= new BufferedInputStream(new FileInputStream(file
))) {
42 mime
= URLConnection
.guessContentTypeFromStream(bufferedStream
);
46 return defaultMimeType
;
51 public static Pair
<StreamDetails
, Optional
<String
>> createStreamDetailsFromDataURI(final String dataURI
) {
52 final DataURI uri
= DataURI
.of(dataURI
);
54 return new Pair
<>(new StreamDetails(
55 new ByteArrayInputStream(uri
.data()), uri
.mediaType(), uri
.data().length
),
56 Optional
.ofNullable(uri
.parameter().get("filename")));
59 public static StreamDetails
createStreamDetailsFromFile(final File file
) throws IOException
{
60 final InputStream stream
= new FileInputStream(file
);
61 final var size
= file
.length();
62 final var mime
= getFileMimeType(file
, "application/octet-stream");
63 return new StreamDetails(stream
, mime
, size
);
66 public static Pair
<StreamDetails
, Optional
<String
>> createStreamDetails(final String value
) throws IOException
{
68 return createStreamDetailsFromDataURI(value
);
69 } catch (final IllegalArgumentException e
) {
70 final File f
= new File(value
);
72 return new Pair
<>(createStreamDetailsFromFile(f
), Optional
.of(f
.getName()));
76 public static Fingerprint
computeSafetyNumber(
77 boolean isUuidCapable
,
78 SignalServiceAddress ownAddress
,
79 IdentityKey ownIdentityKey
,
80 SignalServiceAddress theirAddress
,
81 IdentityKey theirIdentityKey
88 // Version 2: UUID user
90 ownId
= ownAddress
.getServiceId().toByteArray();
91 theirId
= theirAddress
.getServiceId().toByteArray();
93 // Version 1: E164 user
95 if (ownAddress
.getNumber().isEmpty() || theirAddress
.getNumber().isEmpty()) {
98 ownId
= ownAddress
.getNumber().get().getBytes();
99 theirId
= theirAddress
.getNumber().get().getBytes();
102 return new NumericFingerprintGenerator(5200).createFor(version
,
109 public static Locale
getDefaultLocale(Locale fallback
) {
110 final var locale
= Locale
.getDefault();
111 if (locale
== null) {
115 Locale
.LanguageRange
.parse(locale
.getLanguage() + "-" + locale
.getCountry());
116 } catch (IllegalArgumentException e
) {
117 logger
.debug("Invalid locale, ignoring: {}", locale
);
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()) {
130 public boolean tryAdvance(Consumer
<?
super T
> action
) {
131 return lefts
.tryAdvance(left
-> rights
.tryAdvance(right
-> action
.accept(combiner
.apply(left
, right
))));
133 }, leftStream
.isParallel() || rightStream
.isParallel());
136 public static Map
<String
, String
> getQueryMap(String query
) {
137 var params
= query
.split("&");
138 var map
= new HashMap
<String
, String
>();
139 for (var param
: params
) {
140 final var paramParts
= param
.split("=");
141 var name
= URLDecoder
.decode(paramParts
[0], StandardCharsets
.UTF_8
);
142 var value
= URLDecoder
.decode(paramParts
[1], StandardCharsets
.UTF_8
);
143 map
.put(name
, value
);