mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-02 04:20:38 +00:00
Added full RFC 2397 support
This commit is contained in:
parent
a341bfbe25
commit
57d57508fb
4 changed files with 86 additions and 36 deletions
|
@ -28,11 +28,8 @@ public class AttachmentUtils {
|
|||
public static SignalServiceAttachmentStream createAttachmentStream(String attachment) throws AttachmentInvalidException {
|
||||
try {
|
||||
final var streamDetails = Utils.createStreamDetails(attachment);
|
||||
final var name = streamDetails.getStream() instanceof FileInputStream
|
||||
? new File(attachment).getName()
|
||||
: null;
|
||||
|
||||
return createAttachmentStream(streamDetails, Optional.ofNullable(name));
|
||||
return createAttachmentStream(streamDetails.first(), streamDetails.second());
|
||||
} catch (IOException e) {
|
||||
throw new AttachmentInvalidException(attachment, e);
|
||||
}
|
||||
|
|
64
lib/src/main/java/org/asamk/signal/manager/util/DataURI.java
Normal file
64
lib/src/main/java/org/asamk/signal/manager/util/DataURI.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
package org.asamk.signal.manager.util;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Data URI record that follows RFC 2397.
|
||||
*
|
||||
* @param mediaType the media type. If empty, the default media type "text/plain" is used.
|
||||
* @param parameter the list of parameters. Must be URL escaped encoded.
|
||||
* @param data the data. If base64 is not given, the data is treated as ASCII string.
|
||||
*/
|
||||
@SuppressWarnings({"java:S6218"})
|
||||
public record DataURI(String mediaType, Map<String, String> parameter, byte[] data) {
|
||||
|
||||
public static final Pattern DATA_URI_PATTERN = Pattern.compile(
|
||||
"data:(?<type>.+?\\/.+?)?(?<parameters>;.+?)?(?<base64>;base64)?,(?<data>.+)",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern PARAMETER_PATTERN = Pattern.compile("\\G;(?<key>.+)=(?<value>.+)",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
public static final String DEFAULT_TYPE = "text/plain";
|
||||
|
||||
/**
|
||||
* Generates a new {@link DataURI} object from the given string.
|
||||
*
|
||||
* @param dataURI the data URI
|
||||
* @return a data URI object
|
||||
* @throws IllegalArgumentException if the given string is not a valid data URI
|
||||
*/
|
||||
public static DataURI of(final String dataURI) {
|
||||
final var matcher = DATA_URI_PATTERN.matcher(dataURI);
|
||||
|
||||
if (!matcher.find()) {
|
||||
throw new IllegalArgumentException("The given string is not a valid data URI.");
|
||||
}
|
||||
|
||||
final Map<String, String> parameters = new HashMap<>();
|
||||
final var params = matcher.group("parameters");
|
||||
if (params != null) {
|
||||
final Matcher paramsMatcher = PARAMETER_PATTERN.matcher(params);
|
||||
while (paramsMatcher.find()) {
|
||||
final var key = paramsMatcher.group("key");
|
||||
final var value = URLDecoder.decode(paramsMatcher.group("value"), StandardCharsets.UTF_8);
|
||||
parameters.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
final boolean isBase64 = matcher.group("base64") != null;
|
||||
final byte[] data;
|
||||
if (isBase64) {
|
||||
data = Base64.getDecoder().decode(matcher.group("data").getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
data = URLDecoder.decode(matcher.group("data"), StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
return new DataURI(Optional.ofNullable(matcher.group("type")).orElse(DEFAULT_TYPE), parameters, data);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.asamk.signal.manager.util;
|
||||
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.signal.libsignal.protocol.IdentityKey;
|
||||
import org.signal.libsignal.protocol.fingerprint.Fingerprint;
|
||||
import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator;
|
||||
|
@ -22,6 +23,7 @@ import java.util.Base64;
|
|||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.BiFunction;
|
||||
|
@ -33,10 +35,10 @@ public class Utils {
|
|||
|
||||
private final static Logger logger = LoggerFactory.getLogger(Utils.class);
|
||||
|
||||
public static String getFileMimeType(File file, String defaultMimeType) throws IOException {
|
||||
public static String getFileMimeType(final File file, final String defaultMimeType) throws IOException {
|
||||
var mime = Files.probeContentType(file.toPath());
|
||||
if (mime == null) {
|
||||
try (InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
try (final InputStream bufferedStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
mime = URLConnection.guessContentTypeFromStream(bufferedStream);
|
||||
}
|
||||
}
|
||||
|
@ -46,42 +48,29 @@ public class Utils {
|
|||
return mime;
|
||||
}
|
||||
|
||||
private static boolean isBase64DataString(final String[] parts) {
|
||||
return parts.length == 2
|
||||
&& parts[0].startsWith("data:")
|
||||
&& parts[0].contains("/")
|
||||
&& parts[1].startsWith("base64,");
|
||||
public static Pair<StreamDetails, Optional<String>> createStreamDetailsFromDataURI(final String dataURI) {
|
||||
final DataURI uri = DataURI.of(dataURI);
|
||||
|
||||
return new Pair<>(new StreamDetails(
|
||||
new ByteArrayInputStream(uri.data()), uri.mediaType(), uri.data().length),
|
||||
Optional.ofNullable(uri.parameter().get("filename")));
|
||||
}
|
||||
|
||||
public static boolean isBase64DataString(final String value) {
|
||||
return isBase64DataString(value.split(";", 2));
|
||||
}
|
||||
|
||||
public static StreamDetails createStreamDetailsFromBase64(final String base64) {
|
||||
final String[] parts = base64.split(";", 2);
|
||||
if (!isBase64DataString(parts)) {
|
||||
throw new IllegalArgumentException("The given argument is not a valid base64 string.");
|
||||
}
|
||||
|
||||
parts[0] = parts[0].substring(5);
|
||||
final byte[] bytes = Base64.getDecoder().decode(parts[1].substring(7).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return new StreamDetails(new ByteArrayInputStream(bytes), parts[0], bytes.length);
|
||||
}
|
||||
|
||||
public static StreamDetails createStreamDetailsFromFile(File file) throws IOException {
|
||||
InputStream stream = new FileInputStream(file);
|
||||
public static StreamDetails createStreamDetailsFromFile(final File file) throws IOException {
|
||||
final InputStream stream = new FileInputStream(file);
|
||||
final var size = file.length();
|
||||
final var mime = getFileMimeType(file, "application/octet-stream");
|
||||
return new StreamDetails(stream, mime, size);
|
||||
}
|
||||
|
||||
public static StreamDetails createStreamDetails(final String value) throws IOException {
|
||||
if (isBase64DataString(value)) {
|
||||
return createStreamDetailsFromBase64(value);
|
||||
}
|
||||
public static Pair<StreamDetails, Optional<String>> createStreamDetails(final String value) throws IOException {
|
||||
try {
|
||||
return createStreamDetailsFromDataURI(value);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
final File f = new File(value);
|
||||
|
||||
return createStreamDetailsFromFile(new File(value));
|
||||
return new Pair<>(createStreamDetailsFromFile(f), Optional.of(f.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Fingerprint computeSafetyNumber(
|
||||
|
|
|
@ -56,8 +56,8 @@ public class SendCommand implements JsonRpcLocalCommand {
|
|||
.action(Arguments.storeTrue())
|
||||
.help("Read the message from standard input.");
|
||||
subparser.addArgument("-a", "--attachment").nargs("*").help("Add file as attachment."
|
||||
+ "Base64 encoded attachments can be added and must follow the format "
|
||||
+ "data:<MIME-TYPE>;base64,<BASE64 ENCODED DATA>.");
|
||||
+ "Data URI encoded attachments can be added and must follow the RFC 2397. Additionally a file name can be added, e.g. "
|
||||
+ "data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>.");
|
||||
subparser.addArgument("-e", "--end-session", "--endsession")
|
||||
.help("Clear session state and send end session message.")
|
||||
.action(Arguments.storeTrue());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue