1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.api
.AttachmentInvalidException
;
4 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
5 import org
.asamk
.signal
.manager
.internal
.SignalDependencies
;
6 import org
.asamk
.signal
.manager
.storage
.AttachmentStore
;
7 import org
.asamk
.signal
.manager
.util
.AttachmentUtils
;
8 import org
.asamk
.signal
.manager
.util
.IOUtils
;
9 import org
.signal
.libsignal
.protocol
.InvalidMessageException
;
10 import org
.slf4j
.Logger
;
11 import org
.slf4j
.LoggerFactory
;
12 import org
.whispersystems
.signalservice
.api
.crypto
.AttachmentCipherInputStream
;
13 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachment
;
14 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentPointer
;
15 import org
.whispersystems
.signalservice
.api
.messages
.SignalServiceAttachmentStream
;
16 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.MissingConfigurationException
;
17 import org
.whispersystems
.signalservice
.api
.util
.StreamDetails
;
20 import java
.io
.IOException
;
21 import java
.io
.InputStream
;
22 import java
.io
.OutputStream
;
23 import java
.nio
.file
.Files
;
24 import java
.util
.ArrayList
;
25 import java
.util
.List
;
27 public class AttachmentHelper
{
29 private static final Logger logger
= LoggerFactory
.getLogger(AttachmentHelper
.class);
31 private final SignalDependencies dependencies
;
32 private final AttachmentStore attachmentStore
;
34 public AttachmentHelper(final Context context
) {
35 this.dependencies
= context
.getDependencies();
36 this.attachmentStore
= context
.getAttachmentStore();
39 public File
getAttachmentFile(SignalServiceAttachmentPointer pointer
) {
40 return attachmentStore
.getAttachmentFile(pointer
);
43 public StreamDetails
retrieveAttachment(final String id
) throws IOException
{
44 return attachmentStore
.retrieveAttachment(id
);
47 public List
<SignalServiceAttachment
> uploadAttachments(final List
<String
> attachments
) throws AttachmentInvalidException
, IOException
{
48 final var attachmentStreams
= createAttachmentStreams(attachments
);
51 // Upload attachments here, so we only upload once even for multiple recipients
52 final var attachmentPointers
= new ArrayList
<SignalServiceAttachment
>(attachmentStreams
.size());
53 for (final var attachmentStream
: attachmentStreams
) {
54 attachmentPointers
.add(uploadAttachment(attachmentStream
));
56 return attachmentPointers
;
58 for (final var attachmentStream
: attachmentStreams
) {
59 attachmentStream
.close();
64 private List
<SignalServiceAttachmentStream
> createAttachmentStreams(List
<String
> attachments
) throws AttachmentInvalidException
, IOException
{
65 if (attachments
== null) {
68 final var signalServiceAttachments
= new ArrayList
<SignalServiceAttachmentStream
>(attachments
.size());
69 for (var attachment
: attachments
) {
70 final var uploadSpec
= dependencies
.getMessageSender().getResumableUploadSpec();
71 signalServiceAttachments
.add(AttachmentUtils
.createAttachmentStream(attachment
, uploadSpec
));
73 return signalServiceAttachments
;
76 public SignalServiceAttachmentPointer
uploadAttachment(String attachment
) throws IOException
, AttachmentInvalidException
{
77 final var uploadSpec
= dependencies
.getMessageSender().getResumableUploadSpec();
78 var attachmentStream
= AttachmentUtils
.createAttachmentStream(attachment
, uploadSpec
);
79 return uploadAttachment(attachmentStream
);
82 public SignalServiceAttachmentPointer
uploadAttachment(SignalServiceAttachmentStream attachment
) throws IOException
{
83 var messageSender
= dependencies
.getMessageSender();
84 return messageSender
.uploadAttachment(attachment
);
87 public void downloadAttachment(final SignalServiceAttachment attachment
) {
88 if (!attachment
.isPointer()) {
89 logger
.warn("Invalid state, can't store an attachment stream.");
92 var pointer
= attachment
.asPointer();
93 if (pointer
.getPreview().isPresent()) {
94 final var preview
= pointer
.getPreview().get();
96 attachmentStore
.storeAttachmentPreview(pointer
,
97 outputStream
-> outputStream
.write(preview
, 0, preview
.length
));
98 } catch (IOException e
) {
99 logger
.warn("Failed to download attachment preview, ignoring: {}", e
.getMessage());
104 attachmentStore
.storeAttachment(pointer
, outputStream
-> this.retrieveAttachment(pointer
, outputStream
));
105 } catch (IOException e
) {
106 logger
.warn("Failed to download attachment ({}), ignoring: {}", pointer
.getRemoteId(), e
.getMessage());
110 void retrieveAttachment(SignalServiceAttachment attachment
, OutputStream outputStream
) throws IOException
{
111 retrieveAttachment(attachment
, input
-> IOUtils
.copyStream(input
, outputStream
));
114 public void retrieveAttachment(SignalServiceAttachment attachment
, AttachmentHandler consumer
) throws IOException
{
115 if (attachment
.isStream()) {
116 var input
= attachment
.asStream().getInputStream();
117 // don't close input stream here, it might be reused later (e.g. with contact sync messages ...)
118 consumer
.handle(input
);
122 final var pointer
= attachment
.asPointer();
123 logger
.debug("Retrieving attachment {} with size {}", pointer
.getRemoteId(), pointer
.getSize());
124 var tmpFile
= IOUtils
.createTempFile();
125 try (var input
= retrieveAttachmentAsStream(pointer
, tmpFile
)) {
126 consumer
.handle(input
);
129 Files
.delete(tmpFile
.toPath());
130 } catch (IOException e
) {
131 logger
.warn("Failed to delete received attachment temp file “{}”, ignoring: {}",
138 private InputStream
retrieveAttachmentAsStream(
139 SignalServiceAttachmentPointer pointer
,
141 ) throws IOException
{
142 if (pointer
.getDigest().isEmpty()) {
143 throw new IOException("Attachment pointer has no digest.");
146 return dependencies
.getMessageReceiver()
147 .retrieveAttachment(pointer
,
149 ServiceConfig
.MAX_ATTACHMENT_SIZE
,
150 AttachmentCipherInputStream
.IntegrityCheck
.forEncryptedDigest(pointer
.getDigest().get()));
151 } catch (MissingConfigurationException
| InvalidMessageException e
) {
152 throw new IOException(e
);
157 public interface AttachmentHandler
{
159 void handle(InputStream inputStream
) throws IOException
;