Honor attachment filenames.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-04-13 12:54:03 -04:00
parent bc10aea202
commit 40dcc7c873
15 changed files with 113 additions and 18 deletions

View File

@ -75,7 +75,8 @@ NS_ASSUME_NONNULL_BEGIN
key:attachmentProto.key
digest:digest
contentType:attachmentProto.contentType
relay:relay];
relay:relay
filename:attachmentProto.fileName];
[attachmentIds addObject:pointer.uniqueId];

View File

@ -21,9 +21,11 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) {
key:(NSData *)key
digest:(nullable NSData *)digest
contentType:(NSString *)contentType
relay:(NSString *)relay NS_DESIGNATED_INITIALIZER;
relay:(NSString *)relay
filename:(nullable NSString *)filename NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) NSString *relay;
@property (nonatomic, readonly, nullable) NSString *filename;
@property (atomic) TSAttachmentPointerState state;
// Though now required, `digest` may be null for pre-existing records or from

View File

@ -30,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
digest:(nullable NSData *)digest
contentType:(NSString *)contentType
relay:(NSString *)relay
filename:(nullable NSString *)filename
{
self = [super initWithServerId:serverId encryptionKey:key contentType:contentType];
if (!self) {
@ -39,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
_digest = digest;
_state = TSAttachmentPointerStateEnqueued;
_relay = relay;
_filename = filename;
return self;
}

View File

@ -24,6 +24,8 @@ NS_ASSUME_NONNULL_BEGIN
// This only applies for attachments being uploaded.
@property (atomic) BOOL isUploaded;
@property (nonatomic, readonly, nullable) NSString *filename;
#if TARGET_OS_IPHONE
- (nullable UIImage *)image;
#endif

View File

@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
// state, but this constructor is used only for new incoming
// attachments which don't need to be uploaded.
_isUploaded = YES;
_filename = pointer.filename;
return self;
}
@ -115,6 +116,7 @@ NS_ASSUME_NONNULL_BEGIN
{
return [MIMETypeUtil filePathForAttachment:self.uniqueId
ofMIMEType:self.contentType
filename:self.filename
inFolder:[[self class] attachmentsFolder]];
}

View File

@ -1,4 +1,6 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSSyncContactsMessage.h"
#import "Contact.h"
@ -39,7 +41,7 @@ NS_ASSUME_NONNULL_BEGIN
}
OWSSignalServiceProtosAttachmentPointer *attachmentProto =
[self buildAttachmentProtoForAttachmentId:self.attachmentIds[0]];
[self buildAttachmentProtoForAttachmentId:self.attachmentIds[0] filename:nil];
OWSSignalServiceProtosSyncMessageContactsBuilder *contactsBuilder =
[OWSSignalServiceProtosSyncMessageContactsBuilder new];

View File

@ -1,4 +1,6 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSSyncGroupsMessage.h"
#import "NSDate+millisecondTimeStamp.h"
@ -25,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
(unsigned long)self.attachmentIds.count);
}
OWSSignalServiceProtosAttachmentPointer *attachmentProto =
[self buildAttachmentProtoForAttachmentId:self.attachmentIds[0]];
[self buildAttachmentProtoForAttachmentId:self.attachmentIds[0] filename:nil];
OWSSignalServiceProtosSyncMessageGroupsBuilder *groupsBuilder =
[OWSSignalServiceProtosSyncMessageGroupsBuilder new];

View File

@ -23,6 +23,7 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
@interface TSMessage : TSInteraction
@property (nonatomic, readonly) NSMutableArray<NSString *> *attachmentIds;
// A map of attachment id-to-filename.
@property (nullable, nonatomic) NSString *body;
@property (nonatomic) TSGroupMetaMessage groupMetaMessage;
@property (nonatomic) uint32_t expiresInSeconds;

View File

@ -128,7 +128,6 @@ static const NSUInteger OWSMessageSchemaVersion = 3;
}
if (!_attachmentIds) {
// previously allowed nil _attachmentIds
_attachmentIds = [NSMutableArray new];
}

View File

@ -57,6 +57,7 @@ typedef NS_ENUM(NSInteger, TSOutgoingMessageState) {
@property BOOL hasSyncedTranscript;
@property NSString *customMessage;
@property (atomic, readonly) NSString *mostRecentFailureText;
@property (nonatomic, readonly) NSMutableDictionary<NSString *, NSString *> *attachmentFilenameMap;
/**
* Whether the message should be serialized as a modern aka Content, or the old style legacy message.
@ -94,10 +95,14 @@ typedef NS_ENUM(NSInteger, TSOutgoingMessageState) {
* @param attachmentId
* id of an AttachmentStream containing the meta data used when populating the attachment proto
*
* @param filename
* optional filename of the attachment.
*
* @return
* An attachment pointer protobuf suitable for including in various container protobuf builders
*/
- (OWSSignalServiceProtosAttachmentPointer *)buildAttachmentProtoForAttachmentId:(NSString *)attachmentId;
- (OWSSignalServiceProtosAttachmentPointer *)buildAttachmentProtoForAttachmentId:(NSString *)attachmentId
filename:(nullable NSString *)filename;
@end

View File

@ -15,7 +15,13 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithCoder:(NSCoder *)coder
{
return [super initWithCoder:coder];
self = [super initWithCoder:coder];
if (self) {
if (!_attachmentFilenameMap) {
_attachmentFilenameMap = [NSMutableDictionary new];
}
}
return self;
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp
@ -86,6 +92,7 @@ NS_ASSUME_NONNULL_BEGIN
} else {
self.groupMetaMessage = TSGroupMessageNone;
}
_attachmentFilenameMap = [NSMutableDictionary new];
OWSAssert(self.receivedAtDate);
@ -142,7 +149,8 @@ NS_ASSUME_NONNULL_BEGIN
case TSGroupMessageNew: {
if (gThread.groupModel.groupImage != nil && self.attachmentIds.count == 1) {
attachmentWasGroupAvatar = YES;
[groupBuilder setAvatar:[self buildAttachmentProtoForAttachmentId:self.attachmentIds[0]]];
[groupBuilder
setAvatar:[self buildAttachmentProtoForAttachmentId:self.attachmentIds[0] filename:nil]];
}
[groupBuilder setMembersArray:gThread.groupModel.groupMemberIds];
@ -160,7 +168,8 @@ NS_ASSUME_NONNULL_BEGIN
if (!attachmentWasGroupAvatar) {
NSMutableArray *attachments = [NSMutableArray new];
for (NSString *attachmentId in self.attachmentIds) {
[attachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId]];
NSString *filename = self.attachmentFilenameMap[attachmentId];
[attachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId filename:filename]];
}
[builder setAttachmentsArray:attachments];
}
@ -191,7 +200,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (OWSSignalServiceProtosAttachmentPointer *)buildAttachmentProtoForAttachmentId:(NSString *)attachmentId
filename:(nullable NSString *)filename
{
OWSAssert(attachmentId.length > 0);
TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId];
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
DDLogError(@"Unexpected type for attachment builder: %@", attachment);
@ -202,6 +214,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSSignalServiceProtosAttachmentPointerBuilder *builder = [OWSSignalServiceProtosAttachmentPointerBuilder new];
[builder setId:attachmentStream.serverId];
[builder setContentType:attachmentStream.contentType];
[builder setFileName:filename];
[builder setKey:attachmentStream.encryptionKey];
[builder setDigest:attachmentStream.digest];

View File

@ -55,6 +55,7 @@ NS_SWIFT_NAME(MessageSender)
*/
- (void)sendAttachmentData:(NSData *)attachmentData
contentType:(NSString *)contentType
filename:(nullable NSString *)filename
inMessage:(TSOutgoingMessage *)outgoingMessage
success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler;

View File

@ -437,6 +437,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[self sendAttachmentData:attachmentData
contentType:contentType
filename:nil
inMessage:message
success:successWithDeleteHandler
failure:failureWithDeleteHandler];
@ -444,6 +445,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
- (void)sendAttachmentData:(NSData *)data
contentType:(NSString *)contentType
filename:(nullable NSString *)filename
inMessage:(TSOutgoingMessage *)message
success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler
@ -467,6 +469,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[attachmentStream save];
[message.attachmentIds addObject:attachmentStream.uniqueId];
if (filename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = filename;
}
[message save];
dispatch_async(dispatch_get_main_queue(), ^{

View File

@ -29,11 +29,11 @@ extern NSString *const OWSMimeTypeUnknownForTests;
+ (BOOL)isVideo:(NSString *)contentType;
+ (BOOL)isAudio:(NSString *)contentType;
+ (NSString *)filePathForAttachment:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder;
+ (NSString *)filePathForImage:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder;
+ (NSString *)filePathForVideo:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder;
+ (NSString *)filePathForAudio:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder;
+ (NSString *)filePathForAnimated:(NSString *)uniqueId ofMIMEType:(NSString *)contentType inFolder:(NSString *)folder;
// filename is optional and should not be trusted.
+ (NSString *)filePathForAttachment:(NSString *)uniqueId
ofMIMEType:(NSString *)contentType
filename:(nullable NSString *)filename
inFolder:(NSString *)folder;
+ (NSURL *)simLinkCorrectExtensionOfFile:(NSURL *)mediaURL ofMIMEType:(NSString *)contentType;

View File

@ -261,7 +261,65 @@ NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype";
+ (NSString *)filePathForAttachment:(NSString *)uniqueId
ofMIMEType:(NSString *)contentType
inFolder:(NSString *)folder {
filename:(nullable NSString *)filename
inFolder:(NSString *)folder
{
NSString *kDefaultFileExtension = @"bin";
if (filename.length > 0) {
NSString *normalizedFilename =
[filename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
// Ensure that the filename is a valid filesystem name,
// replacing invalid characters with an underscore.
for (NSCharacterSet *invalidCharacterSet in @[
[NSCharacterSet whitespaceCharacterSet],
[NSCharacterSet newlineCharacterSet],
[NSCharacterSet illegalCharacterSet],
[NSCharacterSet controlCharacterSet],
[NSCharacterSet characterSetWithCharactersInString:@"<>|\\:()&;?*"],
]) {
normalizedFilename = [[normalizedFilename componentsSeparatedByCharactersInSet:invalidCharacterSet]
componentsJoinedByString:@"_"];
}
NSString *fileExtension = [[normalizedFilename pathExtension]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *filenameWithoutExtension = [[[normalizedFilename lastPathComponent] stringByDeletingPathExtension]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
// If the filename has not file extension, deduce one
// from the MIME type.
if (fileExtension.length < 1) {
fileExtension = [self fileExtensionForMIMEType:contentType];
if (fileExtension.length < 1) {
fileExtension = kDefaultFileExtension;
}
}
fileExtension = [fileExtension lowercaseString];
if (filenameWithoutExtension.length > 0) {
// Store the file in a subdirectory whose name is the uniqueId of this attachment,
// to avoid collisions between multiple attachments with the same name.
NSString *attachmentFolderPath = [folder stringByAppendingPathComponent:uniqueId];
NSError *error = nil;
BOOL attachmentFolderPathExists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentFolderPath];
if (!attachmentFolderPathExists) {
[[NSFileManager defaultManager] createDirectoryAtPath:attachmentFolderPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
DDLogError(@"Failed to create attachment directory: %@", error);
OWSAssert(0);
return nil;
}
}
return [attachmentFolderPath
stringByAppendingPathComponent:[NSString
stringWithFormat:@"%@.%@", filenameWithoutExtension, fileExtension]];
}
}
if ([self isVideo:contentType]) {
return [MIMETypeUtil filePathForVideo:uniqueId ofMIMEType:contentType inFolder:folder];
} else if ([self isAudio:contentType]) {
@ -291,7 +349,7 @@ NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype";
DDLogError(@"Got asked for path of file %@ which is unsupported", contentType);
// Use a fallback file extension.
return [self filePathForData:uniqueId withFileExtension:@"bin" inFolder:folder];
return [self filePathForData:uniqueId withFileExtension:kDefaultFileExtension inFolder:folder];
}
+ (NSURL *)simLinkCorrectExtensionOfFile:(NSURL *)mediaURL ofMIMEType:(NSString *)contentType {