Extract util methods to separate classes

This commit is contained in:
AsamK 2018-11-18 10:45:26 +01:00
parent 4ab904b88e
commit 7443225d96
6 changed files with 168 additions and 131 deletions

View file

@ -31,7 +31,10 @@ import org.asamk.Signal;
import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.contacts.ContactInfo;
import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.groups.GroupInfo;
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
import org.asamk.signal.util.DateUtils;
import org.asamk.signal.util.Hex; import org.asamk.signal.util.Hex;
import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util;
import org.freedesktop.dbus.DBusConnection; import org.freedesktop.dbus.DBusConnection;
import org.freedesktop.dbus.DBusSigHandler; import org.freedesktop.dbus.DBusSigHandler;
import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusException;
@ -52,15 +55,14 @@ import org.whispersystems.signalservice.internal.util.Base64;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.Security; import java.security.Security;
import java.text.DateFormat; import java.util.ArrayList;
import java.text.SimpleDateFormat; import java.util.List;
import java.util.*; import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -69,8 +71,6 @@ public class Main {
public static final String SIGNAL_BUSNAME = "org.asamk.Signal"; public static final String SIGNAL_BUSNAME = "org.asamk.Signal";
public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal"; public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal";
private static final TimeZone tzUTC = TimeZone.getTimeZone("UTC");
public static void main(String[] args) { public static void main(String[] args) {
// Workaround for BKS truststore // Workaround for BKS truststore
Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1); Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1);
@ -317,8 +317,8 @@ public class Main {
for (DeviceInfo d : devices) { for (DeviceInfo d : devices) {
System.out.println("Device " + d.getId() + (d.getId() == m.getDeviceId() ? " (this device)" : "") + ":"); System.out.println("Device " + d.getId() + (d.getId() == m.getDeviceId() ? " (this device)" : "") + ":");
System.out.println(" Name: " + d.getName()); System.out.println(" Name: " + d.getName());
System.out.println(" Created: " + formatTimestamp(d.getCreated())); System.out.println(" Created: " + DateUtils.formatTimestamp(d.getCreated()));
System.out.println(" Last seen: " + formatTimestamp(d.getLastSeen())); System.out.println(" Last seen: " + DateUtils.formatTimestamp(d.getLastSeen()));
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -373,7 +373,7 @@ public class Main {
String messageText = ns.getString("message"); String messageText = ns.getString("message");
if (messageText == null) { if (messageText == null) {
try { try {
messageText = readAll(System.in); messageText = IOUtils.readAll(System.in, Charset.defaultCharset());
} catch (IOException e) { } catch (IOException e) {
System.err.println("Failed to read message from stdin: " + e.getMessage()); System.err.println("Failed to read message from stdin: " + e.getMessage());
System.err.println("Aborting sending."); System.err.println("Aborting sending.");
@ -425,7 +425,7 @@ public class Main {
@Override @Override
public void handle(Signal.MessageReceived s) { public void handle(Signal.MessageReceived s) {
System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n", System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n",
s.getSender(), formatTimestamp(s.getTimestamp()), s.getMessage())); s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()), s.getMessage()));
if (s.getGroupId().length > 0) { if (s.getGroupId().length > 0) {
System.out.println("Group info:"); System.out.println("Group info:");
System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId())); System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId()));
@ -443,7 +443,7 @@ public class Main {
@Override @Override
public void handle(Signal.ReceiptReceived s) { public void handle(Signal.ReceiptReceived s) {
System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n", System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n",
s.getSender(), formatTimestamp(s.getTimestamp()))); s.getSender(), DateUtils.formatTimestamp(s.getTimestamp())));
} }
}); });
} catch (UnsatisfiedLinkError e) { } catch (UnsatisfiedLinkError e) {
@ -708,7 +708,7 @@ public class Main {
} }
private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) { private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) {
String digits = formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey())); String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey()));
System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername, System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername,
theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toStringCondensed(theirId.getFingerprint()), digits)); theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toStringCondensed(theirId.getFingerprint()), digits));
} }
@ -723,16 +723,6 @@ public class Main {
} }
} }
private static String formatSafetyNumber(String digits) {
final int partCount = 12;
int partSize = digits.length() / partCount;
StringBuilder f = new StringBuilder(digits.length() + partCount);
for (int i = 0; i < partCount; i++) {
f.append(digits.substring(i * partSize, (i * partSize) + partSize)).append(" ");
}
return f.toString();
}
private static void handleGroupNotFoundException(GroupNotFoundException e) { private static void handleGroupNotFoundException(GroupNotFoundException e) {
System.err.println("Failed to send to group: " + e.getMessage()); System.err.println("Failed to send to group: " + e.getMessage());
System.err.println("Aborting sending."); System.err.println("Aborting sending.");
@ -956,18 +946,6 @@ public class Main {
System.err.println("Failed to send message: " + e.getMessage()); System.err.println("Failed to send message: " + e.getMessage());
} }
private static String readAll(InputStream in) throws IOException {
StringWriter output = new StringWriter();
byte[] buffer = new byte[4096];
long count = 0;
int n;
while (-1 != (n = System.in.read(buffer))) {
output.write(new String(buffer, 0, n, Charset.defaultCharset()));
count += n;
}
return output.toString();
}
private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler {
final Manager m; final Manager m;
@ -983,7 +961,7 @@ public class Main {
if (source.getRelay().isPresent()) { if (source.getRelay().isPresent()) {
System.out.println("Relayed by: " + source.getRelay().get()); System.out.println("Relayed by: " + source.getRelay().get());
} }
System.out.println("Timestamp: " + formatTimestamp(envelope.getTimestamp())); System.out.println("Timestamp: " + DateUtils.formatTimestamp(envelope.getTimestamp()));
if (envelope.isUnidentifiedSender()) { if (envelope.isUnidentifiedSender()) {
System.out.println("Sent by unidentified/sealed sender"); System.out.println("Sent by unidentified/sealed sender");
} }
@ -1029,7 +1007,7 @@ public class Main {
System.out.println("Received sync read messages list"); System.out.println("Received sync read messages list");
for (ReadMessage rm : syncMessage.getRead().get()) { for (ReadMessage rm : syncMessage.getRead().get()) {
ContactInfo fromContact = m.getContact(rm.getSender()); ContactInfo fromContact = m.getContact(rm.getSender());
System.out.println("From: " + (fromContact == null ? "" : "" + fromContact.name + "") + rm.getSender() + " Message timestamp: " + formatTimestamp(rm.getTimestamp())); System.out.println("From: " + (fromContact == null ? "" : "" + fromContact.name + "") + rm.getSender() + " Message timestamp: " + DateUtils.formatTimestamp(rm.getTimestamp()));
} }
} }
if (syncMessage.getRequest().isPresent()) { if (syncMessage.getRequest().isPresent()) {
@ -1052,9 +1030,9 @@ public class Main {
} else { } else {
to = "Unknown"; to = "Unknown";
} }
System.out.println("To: " + to + " , Message timestamp: " + formatTimestamp(sentTranscriptMessage.getTimestamp())); System.out.println("To: " + to + " , Message timestamp: " + DateUtils.formatTimestamp(sentTranscriptMessage.getTimestamp()));
if (sentTranscriptMessage.getExpirationStartTimestamp() > 0) { if (sentTranscriptMessage.getExpirationStartTimestamp() > 0) {
System.out.println("Expiration started at: " + formatTimestamp(sentTranscriptMessage.getExpirationStartTimestamp())); System.out.println("Expiration started at: " + DateUtils.formatTimestamp(sentTranscriptMessage.getExpirationStartTimestamp()));
} }
SignalServiceDataMessage message = sentTranscriptMessage.getMessage(); SignalServiceDataMessage message = sentTranscriptMessage.getMessage();
handleSignalServiceDataMessage(message); handleSignalServiceDataMessage(message);
@ -1071,7 +1049,7 @@ public class Main {
System.out.println("Received sync message with verified identities:"); System.out.println("Received sync message with verified identities:");
final VerifiedMessage verifiedMessage = syncMessage.getVerified().get(); final VerifiedMessage verifiedMessage = syncMessage.getVerified().get();
System.out.println(" - " + verifiedMessage.getDestination() + ": " + verifiedMessage.getVerified()); System.out.println(" - " + verifiedMessage.getDestination() + ": " + verifiedMessage.getVerified());
String safetyNumber = formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey())); String safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey()));
System.out.println(" " + safetyNumber); System.out.println(" " + safetyNumber);
} }
if (syncMessage.getConfiguration().isPresent()) { if (syncMessage.getConfiguration().isPresent()) {
@ -1111,7 +1089,7 @@ public class Main {
if (content.getReceiptMessage().isPresent()) { if (content.getReceiptMessage().isPresent()) {
System.out.println("Received a receipt message"); System.out.println("Received a receipt message");
SignalServiceReceiptMessage receiptMessage = content.getReceiptMessage().get(); SignalServiceReceiptMessage receiptMessage = content.getReceiptMessage().get();
System.out.println(" - When: " + formatTimestamp(receiptMessage.getWhen())); System.out.println(" - When: " + DateUtils.formatTimestamp(receiptMessage.getWhen()));
if (receiptMessage.isDeliveryReceipt()) { if (receiptMessage.isDeliveryReceipt()) {
System.out.println(" - Is delivery receipt"); System.out.println(" - Is delivery receipt");
} }
@ -1120,14 +1098,14 @@ public class Main {
} }
System.out.println(" - Timestamps:"); System.out.println(" - Timestamps:");
for (long timestamp : receiptMessage.getTimestamps()) { for (long timestamp : receiptMessage.getTimestamps()) {
System.out.println(" " + formatTimestamp(timestamp)); System.out.println(" " + DateUtils.formatTimestamp(timestamp));
} }
} }
if (content.getTypingMessage().isPresent()) { if (content.getTypingMessage().isPresent()) {
System.out.println("Received a typing message"); System.out.println("Received a typing message");
SignalServiceTypingMessage typingMessage = content.getTypingMessage().get(); SignalServiceTypingMessage typingMessage = content.getTypingMessage().get();
System.out.println(" - Action: " + typingMessage.getAction()); System.out.println(" - Action: " + typingMessage.getAction());
System.out.println(" - Timestamp: " + formatTimestamp(typingMessage.getTimestamp())); System.out.println(" - Timestamp: " + DateUtils.formatTimestamp(typingMessage.getTimestamp()));
if (typingMessage.getGroupId().isPresent()) { if (typingMessage.getGroupId().isPresent()) {
GroupInfo group = m.getGroup(typingMessage.getGroupId().get()); GroupInfo group = m.getGroup(typingMessage.getGroupId().get());
if (group != null) { if (group != null) {
@ -1145,7 +1123,7 @@ public class Main {
} }
private void handleSignalServiceDataMessage(SignalServiceDataMessage message) { private void handleSignalServiceDataMessage(SignalServiceDataMessage message) {
System.out.println("Message timestamp: " + formatTimestamp(message.getTimestamp())); System.out.println("Message timestamp: " + DateUtils.formatTimestamp(message.getTimestamp()));
if (message.getBody().isPresent()) { if (message.getBody().isPresent()) {
System.out.println("Body: " + message.getBody().get()); System.out.println("Body: " + message.getBody().get());
@ -1334,11 +1312,4 @@ public class Main {
} }
} }
} }
private static String formatTimestamp(long timestamp) {
Date date = new Date(timestamp);
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
df.setTimeZone(tzUTC);
return timestamp + " (" + df.format(date) + ")";
}
} }

View file

@ -35,6 +35,7 @@ import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
import org.asamk.signal.storage.threads.JsonThreadStore; import org.asamk.signal.storage.threads.JsonThreadStore;
import org.asamk.signal.storage.threads.ThreadInfo; import org.asamk.signal.storage.threads.ThreadInfo;
import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.KeyUtils; import org.asamk.signal.util.KeyUtils;
import org.asamk.signal.util.Util; import org.asamk.signal.util.Util;
import org.signal.libsignal.metadata.*; import org.signal.libsignal.metadata.*;
@ -81,23 +82,17 @@ import org.whispersystems.signalservice.internal.util.Base64;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import static java.nio.file.attribute.PosixFilePermission.*;
class Manager implements Signal { class Manager implements Signal {
private final static String URL = "https://textsecure-service.whispersystems.org"; private final static String URL = "https://textsecure-service.whispersystems.org";
private final static String CDN_URL = "https://cdn.signal.org"; private final static String CDN_URL = "https://cdn.signal.org";
@ -189,30 +184,10 @@ class Manager implements Signal {
private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException { private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException {
String cachePath = getMessageCachePath(sender); String cachePath = getMessageCachePath(sender);
createPrivateDirectories(cachePath); IOUtils.createPrivateDirectories(cachePath);
return new File(cachePath + "/" + now + "_" + timestamp); return new File(cachePath + "/" + now + "_" + timestamp);
} }
private static void createPrivateDirectories(String path) throws IOException {
final Path file = new File(path).toPath();
try {
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms));
} catch (UnsupportedOperationException e) {
Files.createDirectories(file);
}
}
private static void createPrivateFile(String path) throws IOException {
final Path file = new File(path).toPath();
try {
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE);
Files.createFile(file, PosixFilePermissions.asFileAttribute(perms));
} catch (UnsupportedOperationException e) {
Files.createFile(file);
}
}
public boolean userExists() { public boolean userExists() {
if (username == null) { if (username == null) {
return false; return false;
@ -238,9 +213,9 @@ class Manager implements Signal {
if (fileChannel != null) if (fileChannel != null)
return; return;
createPrivateDirectories(dataPath); IOUtils.createPrivateDirectories(dataPath);
if (!new File(getFileName()).exists()) { if (!new File(getFileName()).exists()) {
createPrivateFile(getFileName()); IOUtils.createPrivateFile(getFileName());
} }
fileChannel = new RandomAccessFile(new File(getFileName()), "rw").getChannel(); fileChannel = new RandomAccessFile(new File(getFileName()), "rw").getChannel();
lock = fileChannel.tryLock(); lock = fileChannel.tryLock();
@ -334,7 +309,7 @@ class Manager implements Signal {
File attachmentFile = getAttachmentFile(g.getAvatarId()); File attachmentFile = getAttachmentFile(g.getAvatarId());
if (!avatarFile.exists() && attachmentFile.exists()) { if (!avatarFile.exists() && attachmentFile.exists()) {
try { try {
createPrivateDirectories(avatarsPath); IOUtils.createPrivateDirectories(avatarsPath);
Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) { } catch (Exception e) {
// Ignore // Ignore
@ -459,30 +434,8 @@ class Manager implements Signal {
accountManager.removeDevice(deviceId); accountManager.removeDevice(deviceId);
} }
public static Map<String, String> getQueryMap(String query) {
String[] params = query.split("&");
Map<String, String> map = new HashMap<>();
for (String param : params) {
String name = null;
final String[] paramParts = param.split("=");
try {
name = URLDecoder.decode(paramParts[0], "utf-8");
} catch (UnsupportedEncodingException e) {
// Impossible
}
String value = null;
try {
value = URLDecoder.decode(paramParts[1], "utf-8");
} catch (UnsupportedEncodingException e) {
// Impossible
}
map.put(name, value);
}
return map;
}
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException { public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
Map<String, String> query = getQueryMap(linkUri.getRawQuery()); Map<String, String> query = Util.getQueryMap(linkUri.getRawQuery());
String deviceIdentifier = query.get("uuid"); String deviceIdentifier = query.get("uuid");
String publicKeyEncoded = query.get("pub_key"); String publicKeyEncoded = query.get("pub_key");
@ -672,18 +625,6 @@ class Manager implements Signal {
sendMessageLegacy(messageBuilder, g.members); sendMessageLegacy(messageBuilder, g.members);
} }
private static String join(CharSequence separator, Iterable<? extends CharSequence> list) {
StringBuilder buf = new StringBuilder();
for (CharSequence str : list) {
if (buf.length() > 0) {
buf.append(separator);
}
buf.append(str);
}
return buf.toString();
}
public byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException { public byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<String> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
GroupInfo g; GroupInfo g;
if (groupId == null) { if (groupId == null) {
@ -720,14 +661,14 @@ class Manager implements Signal {
for (ContactTokenDetails contact : contacts) { for (ContactTokenDetails contact : contacts) {
newMembers.remove(contact.getNumber()); newMembers.remove(contact.getNumber());
} }
System.err.println("Failed to add members " + join(", ", newMembers) + " to group: Not registered on Signal"); System.err.println("Failed to add members " + Util.join(", ", newMembers) + " to group: Not registered on Signal");
System.err.println("Aborting…"); System.err.println("Aborting…");
System.exit(1); System.exit(1);
} }
} }
if (avatarFile != null) { if (avatarFile != null) {
createPrivateDirectories(avatarsPath); IOUtils.createPrivateDirectories(avatarsPath);
File aFile = getGroupAvatarFile(g.groupId); File aFile = getGroupAvatarFile(g.groupId);
Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} }
@ -920,7 +861,7 @@ class Manager implements Signal {
return UnidentifiedAccess.deriveAccessKeyFrom(profileKey); return UnidentifiedAccess.deriveAccessKeyFrom(profileKey);
} }
private static byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) {
// TODO implement // TODO implement
return null; return null;
} }
@ -1330,7 +1271,7 @@ class Manager implements Signal {
if (syncMessage.getGroups().isPresent()) { if (syncMessage.getGroups().isPresent()) {
File tmpFile = null; File tmpFile = null;
try { try {
tmpFile = Util.createTempFile(); tmpFile = IOUtils.createTempFile();
try (InputStream attachmentAsStream = retrieveAttachmentAsStream(syncMessage.getGroups().get().asPointer(), tmpFile)) { try (InputStream attachmentAsStream = retrieveAttachmentAsStream(syncMessage.getGroups().get().asPointer(), tmpFile)) {
DeviceGroupsInputStream s = new DeviceGroupsInputStream(attachmentAsStream); DeviceGroupsInputStream s = new DeviceGroupsInputStream(attachmentAsStream);
DeviceGroup g; DeviceGroup g;
@ -1372,7 +1313,7 @@ class Manager implements Signal {
if (syncMessage.getContacts().isPresent()) { if (syncMessage.getContacts().isPresent()) {
File tmpFile = null; File tmpFile = null;
try { try {
tmpFile = Util.createTempFile(); tmpFile = IOUtils.createTempFile();
final ContactsMessage contactsMessage = syncMessage.getContacts().get(); final ContactsMessage contactsMessage = syncMessage.getContacts().get();
try (InputStream attachmentAsStream = retrieveAttachmentAsStream(contactsMessage.getContactsStream().asPointer(), tmpFile)) { try (InputStream attachmentAsStream = retrieveAttachmentAsStream(contactsMessage.getContactsStream().asPointer(), tmpFile)) {
DeviceContactsInputStream s = new DeviceContactsInputStream(attachmentAsStream); DeviceContactsInputStream s = new DeviceContactsInputStream(attachmentAsStream);
@ -1506,7 +1447,7 @@ class Manager implements Signal {
} }
private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException { private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException {
createPrivateDirectories(avatarsPath); IOUtils.createPrivateDirectories(avatarsPath);
if (attachment.isPointer()) { if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer(); SignalServiceAttachmentPointer pointer = attachment.asPointer();
return retrieveAttachment(pointer, getContactAvatarFile(number), false); return retrieveAttachment(pointer, getContactAvatarFile(number), false);
@ -1521,7 +1462,7 @@ class Manager implements Signal {
} }
private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException { private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException {
createPrivateDirectories(avatarsPath); IOUtils.createPrivateDirectories(avatarsPath);
if (attachment.isPointer()) { if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer(); SignalServiceAttachmentPointer pointer = attachment.asPointer();
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false); return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
@ -1536,7 +1477,7 @@ class Manager implements Signal {
} }
private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException { private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException {
createPrivateDirectories(attachmentsPath); IOUtils.createPrivateDirectories(attachmentsPath);
return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true); return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true);
} }
@ -1571,7 +1512,7 @@ class Manager implements Signal {
final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration, username, password, deviceId, signalingKey, USER_AGENT, null, timer); final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration, username, password, deviceId, signalingKey, USER_AGENT, null, timer);
File tmpFile = Util.createTempFile(); File tmpFile = IOUtils.createTempFile();
try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, MAX_ATTACHMENT_SIZE)) { try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, MAX_ATTACHMENT_SIZE)) {
try (OutputStream output = new FileOutputStream(outputFile)) { try (OutputStream output = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
@ -1615,7 +1556,7 @@ class Manager implements Signal {
} }
private void sendGroups() throws IOException, UntrustedIdentityException { private void sendGroups() throws IOException, UntrustedIdentityException {
File groupsFile = Util.createTempFile(); File groupsFile = IOUtils.createTempFile();
try { try {
try (OutputStream fos = new FileOutputStream(groupsFile)) { try (OutputStream fos = new FileOutputStream(groupsFile)) {
@ -1650,7 +1591,7 @@ class Manager implements Signal {
} }
private void sendContacts() throws IOException, UntrustedIdentityException { private void sendContacts() throws IOException, UntrustedIdentityException {
File contactsFile = Util.createTempFile(); File contactsFile = IOUtils.createTempFile();
try { try {
try (OutputStream fos = new FileOutputStream(contactsFile)) { try (OutputStream fos = new FileOutputStream(contactsFile)) {

View file

@ -0,0 +1,21 @@
package org.asamk.signal.util;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class DateUtils {
private static final TimeZone tzUTC = TimeZone.getTimeZone("UTC");
private DateUtils() {
}
public static String formatTimestamp(long timestamp) {
Date date = new Date(timestamp);
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
df.setTimeZone(tzUTC);
return timestamp + " (" + df.format(date) + ")";
}
}

View file

@ -6,6 +6,9 @@ public class Hex {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
}; };
private Hex() {
}
public static String toStringCondensed(byte[] bytes) { public static String toStringCondensed(byte[] bytes) {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
for (int i = 0; i < bytes.length; i++) { for (int i = 0; i < bytes.length; i++) {

View file

@ -0,0 +1,55 @@
package org.asamk.signal.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.EnumSet;
import java.util.Set;
import static java.nio.file.attribute.PosixFilePermission.*;
public class IOUtils {
private IOUtils() {
}
public static File createTempFile() throws IOException {
return File.createTempFile("signal_tmp_", ".tmp");
}
public static String readAll(InputStream in, Charset charset) throws IOException {
StringWriter output = new StringWriter();
byte[] buffer = new byte[4096];
int n;
while (-1 != (n = in.read(buffer))) {
output.write(new String(buffer, 0, n, charset));
}
return output.toString();
}
public static void createPrivateDirectories(String path) throws IOException {
final Path file = new File(path).toPath();
try {
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms));
} catch (UnsupportedOperationException e) {
Files.createDirectories(file);
}
}
public static void createPrivateFile(String path) throws IOException {
final Path file = new File(path).toPath();
try {
Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE);
Files.createFile(file, PosixFilePermissions.asFileAttribute(perms));
} catch (UnsupportedOperationException e) {
Files.createFile(file);
}
}
}

View file

@ -1,10 +1,56 @@
package org.asamk.signal.util; package org.asamk.signal.util;
import java.io.File; import java.io.UnsupportedEncodingException;
import java.io.IOException; import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
public class Util { public class Util {
public static File createTempFile() throws IOException {
return File.createTempFile("signal_tmp_", ".tmp"); private Util() {
}
public static String formatSafetyNumber(String digits) {
final int partCount = 12;
int partSize = digits.length() / partCount;
StringBuilder f = new StringBuilder(digits.length() + partCount);
for (int i = 0; i < partCount; i++) {
f.append(digits, i * partSize, (i * partSize) + partSize).append(" ");
}
return f.toString();
}
public static Map<String, String> getQueryMap(String query) {
String[] params = query.split("&");
Map<String, String> map = new HashMap<>();
for (String param : params) {
String name = null;
final String[] paramParts = param.split("=");
try {
name = URLDecoder.decode(paramParts[0], "utf-8");
} catch (UnsupportedEncodingException e) {
// Impossible
}
String value = null;
try {
value = URLDecoder.decode(paramParts[1], "utf-8");
} catch (UnsupportedEncodingException e) {
// Impossible
}
map.put(name, value);
}
return map;
}
public static String join(CharSequence separator, Iterable<? extends CharSequence> list) {
StringBuilder buf = new StringBuilder();
for (CharSequence str : list) {
if (buf.length() > 0) {
buf.append(separator);
}
buf.append(str);
}
return buf.toString();
} }
} }