From 09e65a674b4e672b7fb094c26758e629f7278ca6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 4 Aug 2017 17:29:48 -0400 Subject: [PATCH] Incomplete work to upload avatars. // FREEBIE --- Signal/src/Profiles/OWSProfileManager.m | 164 +++++++++++++++++++++++- 1 file changed, 159 insertions(+), 5 deletions(-) diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index 347de0a66..6ff382e63 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -10,6 +10,7 @@ #import #import #import +#import #import #import #import @@ -414,11 +415,164 @@ static const NSInteger kProfileKeyLength = 16; // TODO: NSString *avatarUrl = @"avatarUrl"; NSData *avatarDigest = [@"avatarDigest" dataUsingEncoding:NSUTF8StringEncoding]; - if (YES) { - successBlock(avatarUrl, avatarDigest); - return; - } - failureBlock(); + + // See: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html + TSProfileAvatarUploadFormRequest *formRequest = [TSProfileAvatarUploadFormRequest new]; + + [self.networkManager makeRequest:formRequest + success:^(NSURLSessionDataTask *task, id formResponseObject) { + + if (![formResponseObject isKindOfClass:[NSDictionary class]]) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_response"); + failureBlock(); + return; + } + NSDictionary *responseMap = formResponseObject; + DDLogError(@"responseObject: %@", formResponseObject); + // acl = private; + // algorithm = "AWS4-HMAC-SHA256"; + // credential = + // "AKIAINTYCHN42UH3LGRA/20170804/us-east-1/s3/aws4_request"; date = + // 20170804T193927Z; key = PtRO3iSkY6twBA; policy = + // eyAiZXhwaXJhdGlvbiI6ICIyMDE3LTA4LTA0VDIwOjA5OjI3LjMwMFoiLAogICJjb25kaXRpb25zIjogWwogICAgeyJidWNrZXQiOiAic2lnbmFsLXByb2ZpbGVzLXN0YWdpbmcifSwKICAgIHsia2V5IjogIlB0Uk8zaVNrWTZ0d0JBIn0sCiAgICB7ImFjbCI6ICJwcml2YXRlIn0sCiAgICBbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiIl0sCgogICAgeyJ4LWFtei1jcmVkZW50aWFsIjogIkFLSUFJTlRZQ0hONDJVSDNMR1JBLzIwMTcwODA0L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QifSwKICAgIHsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwKICAgIHsieC1hbXotZGF0ZSI6ICIyMDE3MDgwNFQxOTM5MjdaIiB9CiAgXQp9; + // signature = + // 3608fdc9af8ca0d13c754c34eb37014c9995b058c2e0166550468de47b00f316; + // url = "profiles-staging.signal.org"; + + NSString *formUrl = responseMap[@"url"]; + if (![formUrl isKindOfClass:[NSString class]] || formUrl.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_url"); + failureBlock(); + return; + } + NSString *formAcl = responseMap[@"acl"]; + if (![formAcl isKindOfClass:[NSString class]] || formAcl.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_acl"); + failureBlock(); + return; + } + NSString *formKey = responseMap[@"key"]; + if (![formKey isKindOfClass:[NSString class]] || formKey.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_key"); + failureBlock(); + return; + } + NSString *formPolicy = responseMap[@"policy"]; + if (![formPolicy isKindOfClass:[NSString class]] || formPolicy.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_policy"); + failureBlock(); + return; + } + NSString *formAlgorithm = responseMap[@"algorithm"]; + if (![formAlgorithm isKindOfClass:[NSString class]] || formAlgorithm.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_algorithm"); + failureBlock(); + return; + } + NSString *formCredential = responseMap[@"credential"]; + if (![formCredential isKindOfClass:[NSString class]] || formCredential.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_credential"); + failureBlock(); + return; + } + NSString *formDate = responseMap[@"date"]; + if (![formDate isKindOfClass:[NSString class]] || formDate.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_date"); + failureBlock(); + return; + } + NSString *formSignature = responseMap[@"signature"]; + if (![formSignature isKindOfClass:[NSString class]] || formSignature.length < 1) { + OWSProdFail(@"profile_manager_error_avatar_upload_form_invalid_signature"); + failureBlock(); + return; + } + NSDictionary *parameters = @{ + @"acl" : formAcl, + @"x-amz-algorithm" : formAlgorithm, + @"x-amz-credential" : formCredential, + @"x-amz-date" : formDate, + @"key" : formKey, + @"policy" : formPolicy, + @"x-amz-signature" : formSignature, + + }; + + NSString *filePath = [self.profileAvatarsDirPath stringByAppendingPathComponent:fileName]; + NSInputStream *fileInputStream = [[NSInputStream alloc] initWithFileAtPath:filePath]; + if (!fileInputStream) { + OWSProdFail(@"profile_manager_error_avatar_upload_invalid_file_stream"); + failureBlock(); + return; + } + + NSError *error; + long long fileSize = + [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error][NSFileSize] + longLongValue]; + if (error || fileSize <= 0) { + OWSProdFail(@"profile_manager_error_avatar_upload_invalid_file_size"); + failureBlock(); + return; + } + + NSMutableURLRequest *uploadRequest = [[AFHTTPRequestSerializer serializer] + multipartFormRequestWithMethod:@"post" + URLString:[@"https://" stringByAppendingString:formUrl] + parameters:parameters + constructingBodyWithBlock:^(id formData) { + // [formData appendPartWithFormData:<#(nonnull NSData *)#> name:@"acl"]; + + [formData appendPartWithInputStream:fileInputStream + name:@"file" + fileName:fileName + length:fileSize + mimeType:@"image/jpeg"]; + } + error:&error]; + + if (error || !uploadRequest) { + OWSProdFail(@"profile_manager_error_avatar_upload_invalid_upload_request"); + failureBlock(); + return; + } + + [uploadRequest setValue:@"Content-Type: text/html; charset=UTF-8" forHTTPHeaderField:@"Content-Type"]; + + + // [uploadRequest setAllHTTPHeaderFields:headerDictionary]; + + // TODO: Should we use a special configuration as we do in TSNetworkManager? + // TODO: How does censorship circumvention fit in? + AFURLSessionManager *manager = [[AFURLSessionManager alloc] + initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + manager.responseSerializer = [AFXMLParserResponseSerializer new]; + NSURLSessionUploadTask *uploadTask; + uploadTask = [manager uploadTaskWithStreamedRequest:uploadRequest + progress:^(NSProgress *_Nonnull uploadProgress) { + // This is not called back on the main queue. + // You are responsible for dispatching to the main queue for UI updates + + DDLogVerbose(@"%@ Avatar upload progress: %f", self.tag, uploadProgress.fractionCompleted); + } + completionHandler:^(NSURLResponse *_Nonnull response, + id _Nullable uploadResponseObject, + NSError *_Nullable uploadError) { + + if (uploadError) { + DDLogError(@"%@ Avatar upload failed: %@", self.tag, uploadError); + failureBlock(); + } else { + DDLogVerbose(@"%@ Avatar upload succeeded", self.tag); + successBlock(avatarUrl, avatarDigest); + } + }]; + [uploadTask resume]; + } + failure:^(NSURLSessionDataTask *task, NSError *error) { + DDLogError(@"%@ Failed to get profile avatar upload form: %@", self.tag, error); + failureBlock(); + }]; }); }