Merge branch 'charlesmchen/uploadV2'
This commit is contained in:
commit
d56beb4478
2
Pods
2
Pods
@ -1 +1 @@
|
||||
Subproject commit e0e6cd88de3842930e91a8e2fe92d79a72c99da2
|
||||
Subproject commit 413f983f5c99fc2854990f4f63e1a5cf67b9a9d7
|
||||
@ -23,6 +23,7 @@
|
||||
#import <SignalServiceKit/OWSProfileKeyMessage.h>
|
||||
#import <SignalServiceKit/OWSRequestBuilder.h>
|
||||
#import <SignalServiceKit/OWSSignalService.h>
|
||||
#import <SignalServiceKit/OWSUploadV2.h>
|
||||
#import <SignalServiceKit/SSKEnvironment.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/TSGroupThread.h>
|
||||
@ -408,118 +409,28 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
||||
};
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
// See: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
|
||||
TSRequest *formRequest = [OWSRequestFactory profileAvatarUploadFormRequest];
|
||||
NSData *_Nullable encryptedAvatarData;
|
||||
if (avatarData) {
|
||||
encryptedAvatarData = [self encryptProfileData:avatarData];
|
||||
OWSAssertDebug(encryptedAvatarData.length > 0);
|
||||
}
|
||||
|
||||
[self.networkManager makeRequest:formRequest
|
||||
success:^(NSURLSessionDataTask *task, id formResponseObject) {
|
||||
if (avatarData == nil) {
|
||||
OWSLogDebug(@"successfully cleared avatar");
|
||||
clearLocalAvatar();
|
||||
successBlock(nil);
|
||||
return;
|
||||
}
|
||||
OWSAvatarUploadV2 *upload = [OWSAvatarUploadV2 new];
|
||||
[[upload uploadAvatarToService:encryptedAvatarData
|
||||
clearLocalAvatar:clearLocalAvatar
|
||||
progressBlock:^(NSProgress *progress){
|
||||
// Do nothing.
|
||||
}]
|
||||
.thenInBackground(^{
|
||||
OWSLogVerbose(@"Upload complete.");
|
||||
|
||||
if (![formResponseObject isKindOfClass:[NSDictionary class]]) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidResponse]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
NSDictionary *responseMap = formResponseObject;
|
||||
OWSLogError(@"responseObject: %@", formResponseObject);
|
||||
successBlock(upload.urlPath);
|
||||
})
|
||||
.catchInBackground(^(NSError *error) {
|
||||
OWSLogError(@"Failed: %@", error);
|
||||
|
||||
NSString *formAcl = responseMap[@"acl"];
|
||||
if (![formAcl isKindOfClass:[NSString class]] || formAcl.length < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidAcl]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
NSString *formKey = responseMap[@"key"];
|
||||
if (![formKey isKindOfClass:[NSString class]] || formKey.length < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidKey]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
NSString *formPolicy = responseMap[@"policy"];
|
||||
if (![formPolicy isKindOfClass:[NSString class]] || formPolicy.length < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidPolicy]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
NSString *formAlgorithm = responseMap[@"algorithm"];
|
||||
if (![formAlgorithm isKindOfClass:[NSString class]] || formAlgorithm.length < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidAlgorithm]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
NSString *formCredential = responseMap[@"credential"];
|
||||
if (![formCredential isKindOfClass:[NSString class]] || formCredential.length < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidCredential]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
NSString *formDate = responseMap[@"date"];
|
||||
if (![formDate isKindOfClass:[NSString class]] || formDate.length < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidDate]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
NSString *formSignature = responseMap[@"signature"];
|
||||
if (![formSignature isKindOfClass:[NSString class]] || formSignature.length < 1) {
|
||||
OWSProdFail([OWSAnalyticsEvents profileManagerErrorAvatarUploadFormInvalidSignature]);
|
||||
return failureBlock(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeAvatarUploadFailed, @"Avatar upload failed."));
|
||||
}
|
||||
|
||||
[self.avatarHTTPManager POST:@""
|
||||
parameters:nil
|
||||
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||
NSData * (^formDataForString)(NSString *formString) = ^(NSString *formString) {
|
||||
return [formString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
};
|
||||
|
||||
// We have to build up the form manually vs. simply passing in a paramaters dict
|
||||
// because AWS is sensitive to the order of the form params (at least the "key"
|
||||
// field must occur early on).
|
||||
// For consistency, all fields are ordered here in a known working order.
|
||||
[formData appendPartWithFormData:formDataForString(formKey) name:@"key"];
|
||||
[formData appendPartWithFormData:formDataForString(formAcl) name:@"acl"];
|
||||
[formData appendPartWithFormData:formDataForString(formAlgorithm) name:@"x-amz-algorithm"];
|
||||
[formData appendPartWithFormData:formDataForString(formCredential) name:@"x-amz-credential"];
|
||||
[formData appendPartWithFormData:formDataForString(formDate) name:@"x-amz-date"];
|
||||
[formData appendPartWithFormData:formDataForString(formPolicy) name:@"policy"];
|
||||
[formData appendPartWithFormData:formDataForString(formSignature) name:@"x-amz-signature"];
|
||||
[formData appendPartWithFormData:formDataForString(OWSMimeTypeApplicationOctetStream)
|
||||
name:@"Content-Type"];
|
||||
NSData *encryptedAvatarData = [self encryptProfileData:avatarData];
|
||||
OWSAssertDebug(encryptedAvatarData.length > 0);
|
||||
[formData appendPartWithFormData:encryptedAvatarData name:@"file"];
|
||||
|
||||
OWSLogVerbose(@"constructed body");
|
||||
}
|
||||
progress:^(NSProgress *_Nonnull uploadProgress) {
|
||||
OWSLogVerbose(@"avatar upload progress: %.2f%%", uploadProgress.fractionCompleted * 100);
|
||||
}
|
||||
success:^(NSURLSessionDataTask *_Nonnull uploadTask, id _Nullable responseObject) {
|
||||
OWSLogInfo(@"successfully uploaded avatar with key: %@", formKey);
|
||||
successBlock(formKey);
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *_Nullable uploadTask, NSError *error) {
|
||||
OWSLogError(@"uploading avatar failed with error: %@", error);
|
||||
clearLocalAvatar();
|
||||
return failureBlock(error);
|
||||
}];
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
// Only clear the local avatar if we have a response. Otherwise, we
|
||||
// had a network failure and probably didn't reach the service.
|
||||
if (task.response != nil) {
|
||||
clearLocalAvatar();
|
||||
}
|
||||
|
||||
OWSLogError(@"Failed to get profile avatar upload form: %@", error);
|
||||
return failureBlock(error);
|
||||
}];
|
||||
failureBlock(error);
|
||||
}) retainUntilComplete];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSAttachmentDownloads.h"
|
||||
@ -23,6 +23,7 @@
|
||||
#import "TSThread.h"
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
#import <SignalCoreKit/Cryptography.h>
|
||||
#import <SignalServiceKit/OWSSignalService.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
|
||||
@ -100,7 +101,6 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
||||
return SSKEnvironment.shared.networkManager;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (instancetype)init
|
||||
@ -373,63 +373,28 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
||||
if (attachmentPointer.serverId < 100) {
|
||||
OWSLogError(@"Suspicious attachment id: %llu", (unsigned long long)attachmentPointer.serverId);
|
||||
}
|
||||
TSRequest *request = [OWSRequestFactory attachmentRequestWithAttachmentId:attachmentPointer.serverId];
|
||||
|
||||
[self.networkManager makeRequest:request
|
||||
success:^(NSURLSessionDataTask *task, id responseObject) {
|
||||
if (![responseObject isKindOfClass:[NSDictionary class]]) {
|
||||
OWSLogError(@"Failed retrieval of attachment. Response had unexpected format.");
|
||||
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
||||
return markAndHandleFailure(error);
|
||||
dispatch_async([OWSDispatch attachmentsQueue], ^{
|
||||
[self downloadJob:job
|
||||
success:^(NSString *encryptedDataFilePath) {
|
||||
[self decryptAttachmentPath:encryptedDataFilePath
|
||||
attachmentPointer:attachmentPointer
|
||||
success:markAndHandleSuccess
|
||||
failure:markAndHandleFailure];
|
||||
}
|
||||
NSString *location = [(NSDictionary *)responseObject objectForKey:@"location"];
|
||||
if (!location) {
|
||||
OWSLogError(@"Failed retrieval of attachment. Response had no location.");
|
||||
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
||||
return markAndHandleFailure(error);
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch attachmentsQueue], ^{
|
||||
[self downloadFromLocation:location
|
||||
job:job
|
||||
success:^(NSString *encryptedDataFilePath) {
|
||||
[self decryptAttachmentPath:encryptedDataFilePath
|
||||
attachmentPointer:attachmentPointer
|
||||
success:markAndHandleSuccess
|
||||
failure:markAndHandleFailure];
|
||||
}
|
||||
failure:^(NSURLSessionTask *_Nullable task, NSError *error) {
|
||||
if (attachmentPointer.serverId < 100) {
|
||||
// This looks like the symptom of the "frequent 404
|
||||
// downloading attachments with low server ids".
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
|
||||
NSInteger statusCode = [httpResponse statusCode];
|
||||
OWSFailDebug(@"%d Failure with suspicious attachment id: %llu, %@",
|
||||
(int)statusCode,
|
||||
(unsigned long long)attachmentPointer.serverId,
|
||||
error);
|
||||
}
|
||||
markAndHandleFailure(error);
|
||||
}];
|
||||
});
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
if (!IsNSErrorNetworkFailure(error)) {
|
||||
OWSProdError([OWSAnalyticsEvents errorAttachmentRequestFailed]);
|
||||
}
|
||||
OWSLogError(@"Failed retrieval of attachment with error: %@", error);
|
||||
if (attachmentPointer.serverId < 100) {
|
||||
// This _shouldn't_ be the symptom of the "frequent 404
|
||||
// downloading attachments with low server ids".
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
|
||||
NSInteger statusCode = [httpResponse statusCode];
|
||||
OWSFailDebug(@"%d Failure with suspicious attachment id: %llu, %@",
|
||||
(int)statusCode,
|
||||
(unsigned long long)attachmentPointer.serverId,
|
||||
error);
|
||||
}
|
||||
return markAndHandleFailure(error);
|
||||
}];
|
||||
failure:^(NSURLSessionTask *_Nullable task, NSError *error) {
|
||||
if (attachmentPointer.serverId < 100) {
|
||||
// This looks like the symptom of the "frequent 404
|
||||
// downloading attachments with low server ids".
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
|
||||
NSInteger statusCode = [httpResponse statusCode];
|
||||
OWSFailDebug(@"%d Failure with suspicious attachment id: %llu, %@",
|
||||
(int)statusCode,
|
||||
(unsigned long long)attachmentPointer.serverId,
|
||||
error);
|
||||
}
|
||||
markAndHandleFailure(error);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)decryptAttachmentPath:(NSString *)encryptedDataFilePath
|
||||
@ -515,20 +480,25 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
||||
return _serialQueue;
|
||||
}
|
||||
|
||||
- (void)downloadFromLocation:(NSString *)location
|
||||
job:(OWSAttachmentDownloadJob *)job
|
||||
success:(void (^)(NSString *encryptedDataPath))successHandler
|
||||
failure:(void (^)(NSURLSessionTask *_Nullable task, NSError *error))failureHandlerParam
|
||||
- (void)downloadJob:(OWSAttachmentDownloadJob *)job
|
||||
success:(void (^)(NSString *encryptedDataPath))successHandler
|
||||
failure:(void (^)(NSURLSessionTask *_Nullable task, NSError *error))failureHandlerParam
|
||||
{
|
||||
OWSAssertDebug(job);
|
||||
|
||||
TSAttachmentPointer *attachmentPointer = job.attachmentPointer;
|
||||
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
AFHTTPSessionManager *manager = [OWSSignalService sharedInstance].CDNSessionManager;
|
||||
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
|
||||
[manager.requestSerializer setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"];
|
||||
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
|
||||
NSString *urlPath = [NSString stringWithFormat:@"attachments/%llu", attachmentPointer.serverId];
|
||||
OWSLogVerbose(@"urlPath: %@", urlPath);
|
||||
NSURL *url = [[NSURL alloc] initWithString:urlPath relativeToURL:manager.baseURL];
|
||||
OWSLogVerbose(@"url.absoluteString: %@", url.absoluteString);
|
||||
|
||||
// We want to avoid large downloads from a compromised or buggy service.
|
||||
const long kMaxDownloadSize = 150 * 1024 * 1024;
|
||||
__block BOOL hasCheckedContentLength = NO;
|
||||
@ -551,7 +521,7 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
||||
NSString *method = @"GET";
|
||||
NSError *serializationError = nil;
|
||||
NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:method
|
||||
URLString:location
|
||||
URLString:url.absoluteString
|
||||
parameters:nil
|
||||
error:&serializationError];
|
||||
if (serializationError) {
|
||||
|
||||
@ -127,6 +127,11 @@ NS_SWIFT_NAME(init(uniqueId:albumMessageId:attachmentSchemaVersion:attachmentTyp
|
||||
|
||||
#pragma mark - Update With... Methods
|
||||
|
||||
- (void)updateAsUploadedWithEncryptionKey:(NSData *)encryptionKey
|
||||
digest:(NSData *)digest
|
||||
serverId:(UInt64)serverId
|
||||
completion:(dispatch_block_t)completion;
|
||||
|
||||
- (nullable TSAttachmentStream *)cloneAsThumbnail;
|
||||
|
||||
#pragma mark - Protobuf
|
||||
|
||||
@ -847,6 +847,28 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
||||
|
||||
#pragma mark - Update With... Methods
|
||||
|
||||
- (void)updateAsUploadedWithEncryptionKey:(NSData *)encryptionKey
|
||||
digest:(NSData *)digest
|
||||
serverId:(UInt64)serverId
|
||||
completion:(dispatch_block_t)completion
|
||||
{
|
||||
OWSAssertDebug(encryptionKey.length > 0);
|
||||
OWSAssertDebug(digest.length > 0);
|
||||
OWSAssertDebug(serverId > 0);
|
||||
|
||||
[[self dbReadWriteConnection]
|
||||
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[self applyChangeToSelfAndLatestCopy:transaction
|
||||
changeBlock:^(TSAttachmentStream *attachment) {
|
||||
[attachment setEncryptionKey:encryptionKey];
|
||||
[attachment setDigest:digest];
|
||||
[attachment setServerId:serverId];
|
||||
[attachment setIsUploaded:YES];
|
||||
}];
|
||||
}
|
||||
completionBlock:completion];
|
||||
}
|
||||
|
||||
- (nullable TSAttachmentStream *)cloneAsThumbnail
|
||||
{
|
||||
if (!self.isValidVisualMedia) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSUploadOperation.h"
|
||||
@ -10,10 +10,13 @@
|
||||
#import "OWSError.h"
|
||||
#import "OWSOperation.h"
|
||||
#import "OWSRequestFactory.h"
|
||||
#import "OWSUploadV2.h"
|
||||
#import "SSKEnvironment.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSNetworkManager.h"
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
#import <SignalCoreKit/Cryptography.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@ -82,104 +85,26 @@ static const CGFloat kAttachmentUploadProgressTheta = 0.001f;
|
||||
|
||||
[self fireNotificationWithProgress:0];
|
||||
|
||||
OWSLogDebug(@"alloc attachment: %@", self.attachmentId);
|
||||
TSRequest *request = [OWSRequestFactory allocAttachmentRequest];
|
||||
[self.networkManager makeRequest:request
|
||||
success:^(NSURLSessionDataTask *task, id responseObject) {
|
||||
if (![responseObject isKindOfClass:[NSDictionary class]]) {
|
||||
OWSLogError(@"unexpected response from server: %@", responseObject);
|
||||
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
|
||||
OWSAttachmentUploadV2 *upload = [OWSAttachmentUploadV2 new];
|
||||
[[upload uploadAttachmentToService:attachmentStream
|
||||
progressBlock:^(NSProgress *uploadProgress) {
|
||||
[self fireNotificationWithProgress:uploadProgress.fractionCompleted];
|
||||
}]
|
||||
.thenInBackground(^{
|
||||
[attachmentStream updateAsUploadedWithEncryptionKey:upload.encryptionKey
|
||||
digest:upload.digest
|
||||
serverId:upload.serverId
|
||||
completion:^{
|
||||
[self reportSuccess];
|
||||
}];
|
||||
})
|
||||
.catchInBackground(^(NSError *error) {
|
||||
OWSLogError(@"Failed: %@", error);
|
||||
|
||||
error.isRetryable = YES;
|
||||
|
||||
[self reportError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *responseDict = (NSDictionary *)responseObject;
|
||||
UInt64 serverId = ((NSDecimalNumber *)[responseDict objectForKey:@"id"]).unsignedLongLongValue;
|
||||
NSString *location = [responseDict objectForKey:@"location"];
|
||||
|
||||
dispatch_async([OWSDispatch attachmentsQueue], ^{
|
||||
[self uploadWithServerId:serverId location:location attachmentStream:attachmentStream];
|
||||
});
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
OWSLogError(@"Failed to allocate attachment with error: %@", error);
|
||||
error.isRetryable = YES;
|
||||
[self reportError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)uploadWithServerId:(UInt64)serverId
|
||||
location:(NSString *)location
|
||||
attachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
{
|
||||
OWSLogDebug(@"started uploading data for attachment: %@", self.attachmentId);
|
||||
NSError *error;
|
||||
NSData *attachmentData = [attachmentStream readDataFromFileWithError:&error];
|
||||
if (error) {
|
||||
OWSLogError(@"Failed to read attachment data with error: %@", error);
|
||||
error.isRetryable = YES;
|
||||
[self reportError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *encryptionKey;
|
||||
NSData *digest;
|
||||
NSData *_Nullable encryptedAttachmentData =
|
||||
[Cryptography encryptAttachmentData:attachmentData outKey:&encryptionKey outDigest:&digest];
|
||||
if (!encryptedAttachmentData) {
|
||||
OWSFailDebug(@"could not encrypt attachment data.");
|
||||
error = OWSErrorMakeFailedToSendOutgoingMessageError();
|
||||
error.isRetryable = YES;
|
||||
[self reportError:error];
|
||||
return;
|
||||
}
|
||||
attachmentStream.encryptionKey = encryptionKey;
|
||||
attachmentStream.digest = digest;
|
||||
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:location]];
|
||||
request.HTTPMethod = @"PUT";
|
||||
[request setValue:OWSMimeTypeApplicationOctetStream forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
AFURLSessionManager *manager = [[AFURLSessionManager alloc]
|
||||
initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
|
||||
|
||||
NSURLSessionUploadTask *uploadTask;
|
||||
uploadTask = [manager uploadTaskWithRequest:request
|
||||
fromData:encryptedAttachmentData
|
||||
progress:^(NSProgress *_Nonnull uploadProgress) {
|
||||
[self fireNotificationWithProgress:uploadProgress.fractionCompleted];
|
||||
}
|
||||
completionHandler:^(NSURLResponse *_Nonnull response, id _Nullable responseObject, NSError *_Nullable error) {
|
||||
OWSAssertIsOnMainThread();
|
||||
if (error) {
|
||||
error.isRetryable = YES;
|
||||
[self reportError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
|
||||
BOOL isValidResponse = (statusCode >= 200) && (statusCode < 400);
|
||||
if (!isValidResponse) {
|
||||
OWSLogError(@"Unexpected server response: %d", (int)statusCode);
|
||||
NSError *invalidResponseError = OWSErrorMakeUnableToProcessServerResponseError();
|
||||
invalidResponseError.isRetryable = YES;
|
||||
[self reportError:invalidResponseError];
|
||||
return;
|
||||
}
|
||||
|
||||
OWSLogInfo(@"Uploaded attachment: %p serverId: %llu, byteCount: %u",
|
||||
attachmentStream.uniqueId,
|
||||
attachmentStream.serverId,
|
||||
attachmentStream.byteCount);
|
||||
attachmentStream.serverId = serverId;
|
||||
attachmentStream.isUploaded = YES;
|
||||
[attachmentStream saveAsyncWithCompletionBlock:^{
|
||||
[self reportSuccess];
|
||||
}];
|
||||
}];
|
||||
|
||||
[uploadTask resume];
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
|
||||
- (void)fireNotificationWithProgress:(CGFloat)aProgress
|
||||
|
||||
42
SignalServiceKit/src/Network/API/OWSUploadV2.h
Normal file
42
SignalServiceKit/src/Network/API/OWSUploadV2.h
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class AnyPromise;
|
||||
@class TSAttachmentStream;
|
||||
|
||||
typedef void (^UploadProgressBlock)(NSProgress *progress);
|
||||
|
||||
// This class can be safely accessed and used from any thread.
|
||||
@interface OWSAvatarUploadV2 : NSObject
|
||||
|
||||
// This is set on success for non-nil uploads.
|
||||
@property (nonatomic, nullable) NSString *urlPath;
|
||||
|
||||
- (AnyPromise *)uploadAvatarToService:(NSData *_Nullable)avatarData
|
||||
clearLocalAvatar:(dispatch_block_t)clearLocalAvatar
|
||||
progressBlock:(UploadProgressBlock)progressBlock;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// This class can be safely accessed and used from any thread.
|
||||
@interface OWSAttachmentUploadV2 : NSObject
|
||||
|
||||
|
||||
// These properties are set on success.
|
||||
@property (nonatomic, nullable) NSData *encryptionKey;
|
||||
@property (nonatomic, nullable) NSData *digest;
|
||||
@property (nonatomic) UInt64 serverId;
|
||||
|
||||
- (AnyPromise *)uploadAttachmentToService:(TSAttachmentStream *)attachmentStream
|
||||
progressBlock:(UploadProgressBlock)progressBlock;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
445
SignalServiceKit/src/Network/API/OWSUploadV2.m
Normal file
445
SignalServiceKit/src/Network/API/OWSUploadV2.m
Normal file
@ -0,0 +1,445 @@
|
||||
//
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSUploadV2.h"
|
||||
#import <PromiseKit/AnyPromise.h>
|
||||
#import <SignalCoreKit/Cryptography.h>
|
||||
#import <SignalCoreKit/NSData+OWS.h>
|
||||
#import <SignalServiceKit/MIMETypeUtil.h>
|
||||
#import <SignalServiceKit/OWSError.h>
|
||||
#import <SignalServiceKit/OWSRequestFactory.h>
|
||||
#import <SignalServiceKit/OWSSignalService.h>
|
||||
#import <SignalServiceKit/SSKEnvironment.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#import <SignalServiceKit/TSAttachmentStream.h>
|
||||
#import <SignalServiceKit/TSNetworkManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
void AppendMultipartFormPath(id<AFMultipartFormData> formData, NSString *name, NSString *dataString)
|
||||
{
|
||||
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
[formData appendPartWithFormData:data name:name];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSUploadForm : NSObject
|
||||
|
||||
// These properties will bet set for all uploads.
|
||||
@property (nonatomic) NSString *formAcl;
|
||||
@property (nonatomic) NSString *formKey;
|
||||
@property (nonatomic) NSString *formPolicy;
|
||||
@property (nonatomic) NSString *formAlgorithm;
|
||||
@property (nonatomic) NSString *formCredential;
|
||||
@property (nonatomic) NSString *formDate;
|
||||
@property (nonatomic) NSString *formSignature;
|
||||
|
||||
// These properties will bet set for all attachment uploads.
|
||||
@property (nonatomic, nullable) NSNumber *attachmentId;
|
||||
@property (nonatomic, nullable) NSString *attachmentIdString;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
// See: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
|
||||
@implementation OWSUploadForm
|
||||
|
||||
+ (nullable OWSUploadForm *)parse:(NSDictionary *)formResponseObject
|
||||
{
|
||||
if (![formResponseObject isKindOfClass:[NSDictionary class]]) {
|
||||
OWSFailDebug(@"Invalid upload form.");
|
||||
return nil;
|
||||
}
|
||||
NSDictionary *responseMap = formResponseObject;
|
||||
|
||||
NSString *_Nullable formAcl = responseMap[@"acl"];
|
||||
if (![formAcl isKindOfClass:[NSString class]] || formAcl.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: acl.");
|
||||
return nil;
|
||||
}
|
||||
NSString *_Nullable formKey = responseMap[@"key"];
|
||||
if (![formKey isKindOfClass:[NSString class]] || formKey.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: key.");
|
||||
return nil;
|
||||
}
|
||||
NSString *_Nullable formPolicy = responseMap[@"policy"];
|
||||
if (![formPolicy isKindOfClass:[NSString class]] || formPolicy.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: policy.");
|
||||
return nil;
|
||||
}
|
||||
NSString *_Nullable formAlgorithm = responseMap[@"algorithm"];
|
||||
if (![formAlgorithm isKindOfClass:[NSString class]] || formAlgorithm.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: algorithm.");
|
||||
return nil;
|
||||
}
|
||||
NSString *_Nullable formCredential = responseMap[@"credential"];
|
||||
if (![formCredential isKindOfClass:[NSString class]] || formCredential.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: credential.");
|
||||
return nil;
|
||||
}
|
||||
NSString *_Nullable formDate = responseMap[@"date"];
|
||||
if (![formDate isKindOfClass:[NSString class]] || formDate.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: date.");
|
||||
return nil;
|
||||
}
|
||||
NSString *_Nullable formSignature = responseMap[@"signature"];
|
||||
if (![formSignature isKindOfClass:[NSString class]] || formSignature.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: signature.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSNumber *_Nullable attachmentId = responseMap[@"attachmentId"];
|
||||
if (attachmentId == nil) {
|
||||
// This value is optional.
|
||||
} else if (![attachmentId isKindOfClass:[NSNumber class]]) {
|
||||
OWSFailDebug(@"Invalid upload form: attachmentId.");
|
||||
return nil;
|
||||
}
|
||||
NSString *_Nullable attachmentIdString = responseMap[@"attachmentIdString"];
|
||||
if (attachmentIdString == nil) {
|
||||
// This value is optional.
|
||||
} else if (![attachmentIdString isKindOfClass:[NSString class]] || attachmentIdString.length < 1) {
|
||||
OWSFailDebug(@"Invalid upload form: attachmentIdString.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
OWSUploadForm *form = [OWSUploadForm new];
|
||||
|
||||
// Required properties.
|
||||
form.formAcl = formAcl;
|
||||
form.formKey = formKey;
|
||||
form.formPolicy = formPolicy;
|
||||
form.formAlgorithm = formAlgorithm;
|
||||
form.formCredential = formCredential;
|
||||
form.formDate = formDate;
|
||||
form.formSignature = formSignature;
|
||||
|
||||
// Optional properties.
|
||||
form.attachmentId = attachmentId;
|
||||
form.attachmentIdString = attachmentIdString;
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
- (void)appendToForm:(id<AFMultipartFormData>)formData
|
||||
{
|
||||
// We have to build up the form manually vs. simply passing in a paramaters dict
|
||||
// because AWS is sensitive to the order of the form params (at least the "key"
|
||||
// field must occur early on).
|
||||
//
|
||||
// For consistency, all fields are ordered here in a known working order.
|
||||
AppendMultipartFormPath(formData, @"key", self.formKey);
|
||||
AppendMultipartFormPath(formData, @"acl", self.formAcl);
|
||||
AppendMultipartFormPath(formData, @"x-amz-algorithm", self.formAlgorithm);
|
||||
AppendMultipartFormPath(formData, @"x-amz-credential", self.formCredential);
|
||||
AppendMultipartFormPath(formData, @"x-amz-date", self.formDate);
|
||||
AppendMultipartFormPath(formData, @"policy", self.formPolicy);
|
||||
AppendMultipartFormPath(formData, @"x-amz-signature", self.formSignature);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSAvatarUploadV2 ()
|
||||
|
||||
@property (nonatomic, nullable) NSData *avatarData;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSAvatarUploadV2
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (AFHTTPSessionManager *)uploadHTTPManager
|
||||
{
|
||||
return [OWSSignalService sharedInstance].CDNSessionManager;
|
||||
}
|
||||
|
||||
- (TSNetworkManager *)networkManager
|
||||
{
|
||||
return SSKEnvironment.shared.networkManager;
|
||||
}
|
||||
|
||||
#pragma mark - Avatars
|
||||
|
||||
// If avatarData is nil, we are clearing the avatar.
|
||||
- (AnyPromise *)uploadAvatarToService:(NSData *_Nullable)avatarData
|
||||
clearLocalAvatar:(dispatch_block_t)clearLocalAvatar
|
||||
progressBlock:(UploadProgressBlock)progressBlock
|
||||
{
|
||||
OWSAssertDebug(avatarData == nil || avatarData.length > 0);
|
||||
self.avatarData = avatarData;
|
||||
|
||||
__weak OWSAvatarUploadV2 *weakSelf = self;
|
||||
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
TSRequest *formRequest = [OWSRequestFactory profileAvatarUploadFormRequest];
|
||||
[self.networkManager makeRequest:formRequest
|
||||
success:^(NSURLSessionDataTask *task, id formResponseObject) {
|
||||
OWSAvatarUploadV2 *_Nullable strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return resolve(OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Upload deallocated"));
|
||||
}
|
||||
|
||||
if (avatarData == nil) {
|
||||
OWSLogDebug(@"successfully cleared avatar");
|
||||
clearLocalAvatar();
|
||||
return resolve(@(1));
|
||||
}
|
||||
|
||||
// TODO: Should we use a non-empty urlPath?
|
||||
[[strongSelf parseFormAndUpload:formResponseObject urlPath:@"" progressBlock:progressBlock]
|
||||
.thenInBackground(^{
|
||||
return resolve(@(1));
|
||||
})
|
||||
.catchInBackground(^(NSError *error) {
|
||||
clearLocalAvatar();
|
||||
|
||||
resolve(error);
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
// Only clear the local avatar if we have a response. Otherwise, we
|
||||
// had a network failure and probably didn't reach the service.
|
||||
if (task.response != nil) {
|
||||
clearLocalAvatar();
|
||||
}
|
||||
|
||||
OWSLogError(@"Failed to get profile avatar upload form: %@", error);
|
||||
resolve(error);
|
||||
}];
|
||||
});
|
||||
}];
|
||||
return promise;
|
||||
}
|
||||
|
||||
- (AnyPromise *)parseFormAndUpload:(id)formResponseObject
|
||||
urlPath:(NSString *)urlPath
|
||||
progressBlock:(UploadProgressBlock)progressBlock
|
||||
{
|
||||
OWSUploadForm *_Nullable form = [OWSUploadForm parse:formResponseObject];
|
||||
if (!form) {
|
||||
return [AnyPromise
|
||||
promiseWithValue:OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Invalid upload form.")];
|
||||
}
|
||||
|
||||
self.urlPath = form.formKey;
|
||||
|
||||
__weak OWSAvatarUploadV2 *weakSelf = self;
|
||||
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
||||
[self.uploadHTTPManager POST:urlPath
|
||||
parameters:nil
|
||||
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
|
||||
OWSAvatarUploadV2 *_Nullable strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return resolve(OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Upload deallocated"));
|
||||
}
|
||||
|
||||
// We have to build up the form manually vs. simply passing in a paramaters dict
|
||||
// because AWS is sensitive to the order of the form params (at least the "key"
|
||||
// field must occur early on).
|
||||
//
|
||||
// For consistency, all fields are ordered here in a known working order.
|
||||
[form appendToForm:formData];
|
||||
AppendMultipartFormPath(formData, @"Content-Type", OWSMimeTypeApplicationOctetStream);
|
||||
|
||||
NSData *_Nullable uploadData = strongSelf.avatarData;
|
||||
if (uploadData.length < 1) {
|
||||
OWSCFailDebug(@"Could not load upload data.");
|
||||
return resolve(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Could not load upload data."));
|
||||
}
|
||||
OWSCAssertDebug(uploadData.length > 0);
|
||||
[formData appendPartWithFormData:uploadData name:@"file"];
|
||||
|
||||
OWSLogVerbose(@"constructed body");
|
||||
}
|
||||
progress:^(NSProgress *progress) {
|
||||
OWSLogVerbose(@"Upload progress: %.2f%%", progress.fractionCompleted * 100);
|
||||
|
||||
progressBlock(progress);
|
||||
}
|
||||
success:^(NSURLSessionDataTask *uploadTask, id _Nullable responseObject) {
|
||||
OWSLogInfo(@"Upload succeeded with key: %@", form.formKey);
|
||||
return resolve(@(1));
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *_Nullable uploadTask, NSError *error) {
|
||||
OWSLogError(@"Upload failed with error: %@", error);
|
||||
resolve(error);
|
||||
}];
|
||||
}];
|
||||
return promise;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Attachments
|
||||
|
||||
@interface OWSAttachmentUploadV2 ()
|
||||
|
||||
@property (nonatomic) TSAttachmentStream *attachmentStream;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSAttachmentUploadV2
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (AFHTTPSessionManager *)uploadHTTPManager
|
||||
{
|
||||
return [OWSSignalService sharedInstance].CDNSessionManager;
|
||||
}
|
||||
|
||||
- (TSNetworkManager *)networkManager
|
||||
{
|
||||
return SSKEnvironment.shared.networkManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (nullable NSData *)attachmentData
|
||||
{
|
||||
OWSAssertDebug(self.attachmentStream);
|
||||
|
||||
NSData *encryptionKey;
|
||||
NSData *digest;
|
||||
NSError *error;
|
||||
NSData *attachmentData = [self.attachmentStream readDataFromFileWithError:&error];
|
||||
if (error) {
|
||||
OWSLogError(@"Failed to read attachment data with error: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *_Nullable encryptedAttachmentData =
|
||||
[Cryptography encryptAttachmentData:attachmentData outKey:&encryptionKey outDigest:&digest];
|
||||
if (!encryptedAttachmentData) {
|
||||
OWSFailDebug(@"could not encrypt attachment data.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.encryptionKey = encryptionKey;
|
||||
self.digest = digest;
|
||||
|
||||
return encryptedAttachmentData;
|
||||
}
|
||||
|
||||
// On success, yields an instance of OWSUploadV2.
|
||||
- (AnyPromise *)uploadAttachmentToService:(TSAttachmentStream *)attachmentStream
|
||||
progressBlock:(UploadProgressBlock)progressBlock
|
||||
{
|
||||
OWSAssertDebug(attachmentStream);
|
||||
|
||||
self.attachmentStream = attachmentStream;
|
||||
|
||||
__weak OWSAttachmentUploadV2 *weakSelf = self;
|
||||
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
TSRequest *formRequest = [OWSRequestFactory allocAttachmentRequest];
|
||||
[self.networkManager makeRequest:formRequest
|
||||
success:^(NSURLSessionDataTask *task, id formResponseObject) {
|
||||
OWSAttachmentUploadV2 *_Nullable strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return resolve(OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Upload deallocated"));
|
||||
}
|
||||
|
||||
[[strongSelf parseFormAndUpload:formResponseObject
|
||||
urlPath:@"attachments/"
|
||||
progressBlock:progressBlock]
|
||||
.thenInBackground(^{
|
||||
resolve(@(1));
|
||||
})
|
||||
.catchInBackground(^(NSError *error) {
|
||||
resolve(error);
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
OWSLogError(@"Failed to get profile avatar upload form: %@", error);
|
||||
resolve(error);
|
||||
}];
|
||||
});
|
||||
}];
|
||||
return promise;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (AnyPromise *)parseFormAndUpload:(id)formResponseObject
|
||||
urlPath:(NSString *)urlPath
|
||||
progressBlock:(UploadProgressBlock)progressBlock
|
||||
{
|
||||
OWSUploadForm *_Nullable form = [OWSUploadForm parse:formResponseObject];
|
||||
if (!form) {
|
||||
return [AnyPromise
|
||||
promiseWithValue:OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Invalid upload form.")];
|
||||
}
|
||||
UInt64 serverId = form.attachmentId.unsignedLongLongValue;
|
||||
if (serverId < 1) {
|
||||
return [AnyPromise
|
||||
promiseWithValue:OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Invalid upload form.")];
|
||||
}
|
||||
|
||||
self.serverId = serverId;
|
||||
|
||||
__weak OWSAttachmentUploadV2 *weakSelf = self;
|
||||
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
||||
[self.uploadHTTPManager POST:urlPath
|
||||
parameters:nil
|
||||
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
|
||||
OWSAttachmentUploadV2 *_Nullable strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return resolve(OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Upload deallocated"));
|
||||
}
|
||||
|
||||
// We have to build up the form manually vs. simply passing in a paramaters dict
|
||||
// because AWS is sensitive to the order of the form params (at least the "key"
|
||||
// field must occur early on).
|
||||
//
|
||||
// For consistency, all fields are ordered here in a known working order.
|
||||
[form appendToForm:formData];
|
||||
AppendMultipartFormPath(formData, @"Content-Type", OWSMimeTypeApplicationOctetStream);
|
||||
|
||||
NSData *_Nullable uploadData = [strongSelf attachmentData];
|
||||
if (uploadData.length < 1) {
|
||||
OWSCFailDebug(@"Could not load upload data.");
|
||||
return resolve(
|
||||
OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Could not load upload data."));
|
||||
}
|
||||
OWSAssertDebug(uploadData.length > 0);
|
||||
[formData appendPartWithFormData:uploadData name:@"file"];
|
||||
|
||||
OWSLogVerbose(@"constructed body");
|
||||
}
|
||||
progress:^(NSProgress *progress) {
|
||||
OWSLogVerbose(@"Upload progress: %.2f%%", progress.fractionCompleted * 100);
|
||||
|
||||
progressBlock(progress);
|
||||
}
|
||||
success:^(NSURLSessionDataTask *uploadTask, id _Nullable responseObject) {
|
||||
OWSAttachmentUploadV2 *_Nullable strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return resolve(OWSErrorWithCodeDescription(OWSErrorCodeUploadFailed, @"Upload deallocated"));
|
||||
}
|
||||
|
||||
OWSLogInfo(@"Upload succeeded with key: %@", form.formKey);
|
||||
return resolve(@(1));
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *_Nullable uploadTask, NSError *error) {
|
||||
OWSLogError(@"Upload failed with error: %@", error);
|
||||
resolve(error);
|
||||
}];
|
||||
}];
|
||||
return promise;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -141,8 +141,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
+ (TSRequest *)allocAttachmentRequest
|
||||
{
|
||||
NSString *path = [NSString stringWithFormat:@"%@", textSecureAttachmentsAPI];
|
||||
return [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"GET" parameters:@{}];
|
||||
return [TSRequest requestWithUrl:[NSURL URLWithString:@"/v2/attachments/form/upload"] method:@"GET" parameters:@{}];
|
||||
}
|
||||
|
||||
+ (TSRequest *)attachmentRequestWithAttachmentId:(UInt64)attachmentId
|
||||
|
||||
@ -52,6 +52,7 @@ typedef NS_ENUM(NSInteger, OWSErrorCode) {
|
||||
OWSErrorCodeAvatarWriteFailed = 777425,
|
||||
OWSErrorCodeAvatarUploadFailed = 777426,
|
||||
OWSErrorCodeNoSessionForTransientMessage,
|
||||
OWSErrorCodeUploadFailed,
|
||||
};
|
||||
|
||||
extern NSString *const OWSErrorRecipientIdentifierKey;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user