mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'dev' into shared-sender-keys-2
This commit is contained in:
commit
e076cba449
|
@ -170,9 +170,17 @@ public class ConversationMediaView: UIView {
|
|||
}
|
||||
|
||||
backgroundColor = (Theme.isDarkThemeEnabled ? .ows_gray90 : .ows_gray05)
|
||||
let progressView = MediaDownloadView(attachmentId: attachmentId, radius: maxMessageWidth * 0.1)
|
||||
self.addSubview(progressView)
|
||||
progressView.autoPinEdgesToSuperviewEdges()
|
||||
let view: UIView
|
||||
if isOnionRouted { // Loki: Due to the way onion routing works we can't get upload progress for those attachments
|
||||
let activityIndicatorView = UIActivityIndicatorView(style: .white)
|
||||
activityIndicatorView.isHidden = false
|
||||
activityIndicatorView.startAnimating()
|
||||
view = activityIndicatorView
|
||||
} else {
|
||||
view = MediaDownloadView(attachmentId: attachmentId, radius: maxMessageWidth * 0.1)
|
||||
}
|
||||
addSubview(view)
|
||||
view.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
|
||||
private func addUploadProgressIfNecessary(_ subview: UIView) -> Bool {
|
||||
|
|
|
@ -1157,43 +1157,13 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
|
||||
OWSLogVerbose(@"downloading profile avatar: %@", userProfile.uniqueId);
|
||||
|
||||
NSString *tempDirectory = OWSTemporaryDirectory();
|
||||
NSString *tempFilePath = [tempDirectory stringByAppendingPathComponent:fileName];
|
||||
|
||||
NSString *profilePictureURL = userProfile.avatarUrlPath;
|
||||
NSError *serializationError;
|
||||
NSMutableURLRequest *request =
|
||||
[self.avatarHTTPManager.requestSerializer requestWithMethod:@"GET"
|
||||
URLString:profilePictureURL
|
||||
parameters:nil
|
||||
error:&serializationError];
|
||||
if (serializationError) {
|
||||
OWSFailDebug(@"serializationError: %@", serializationError);
|
||||
return;
|
||||
}
|
||||
|
||||
NSURLSession* session = [NSURLSession sharedSession];
|
||||
NSURLSessionTask* downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
|
||||
|
||||
[[LKFileServerAPI downloadAttachmentFrom:profilePictureURL].then(^(NSData *data) {
|
||||
@synchronized(self.currentAvatarDownloads)
|
||||
{
|
||||
[self.currentAvatarDownloads removeObject:userProfile.recipientId];
|
||||
}
|
||||
|
||||
if (error) {
|
||||
OWSLogError(@"Dowload failed: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *tempFileUrl = [NSURL fileURLWithPath:tempFilePath];
|
||||
NSError *moveError;
|
||||
if (![fileManager moveItemAtURL:location toURL:tempFileUrl error:&moveError]) {
|
||||
OWSLogError(@"MoveItemAtURL for avatar failed: %@", moveError);
|
||||
return;
|
||||
}
|
||||
|
||||
NSData *_Nullable encryptedData = (error ? nil : [NSData dataWithContentsOfFile:tempFilePath]);
|
||||
NSData *_Nullable encryptedData = data;
|
||||
NSData *_Nullable decryptedData = [self decryptProfileData:encryptedData profileKey:profileKeyAtStart];
|
||||
UIImage *_Nullable image = nil;
|
||||
if (decryptedData) {
|
||||
|
@ -1213,19 +1183,12 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
if (latestUserProfile.avatarUrlPath.length > 0) {
|
||||
[self downloadAvatarForUserProfile:latestUserProfile];
|
||||
}
|
||||
} else if (error) {
|
||||
if ([response isKindOfClass:NSHTTPURLResponse.class]
|
||||
&& ((NSHTTPURLResponse *)response).statusCode == 403) {
|
||||
OWSLogInfo(@"no avatar for: %@", userProfile.recipientId);
|
||||
} else {
|
||||
OWSLogError(@"avatar download for %@ failed with error: %@", userProfile.recipientId, error);
|
||||
}
|
||||
} else if (!encryptedData) {
|
||||
OWSLogError(@"avatar encrypted data for %@ could not be read.", userProfile.recipientId);
|
||||
} else if (!decryptedData) {
|
||||
OWSLogError(@"avatar data for %@ could not be decrypted.", userProfile.recipientId);
|
||||
} else if (!image) {
|
||||
OWSLogError(@"avatar image for %@ could not be loaded with error: %@", userProfile.recipientId, error);
|
||||
OWSLogError(@"avatar image for %@ could not be loaded.", userProfile.recipientId);
|
||||
} else {
|
||||
[self updateProfileAvatarCache:image filename:fileName];
|
||||
|
||||
|
@ -1248,9 +1211,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
|
|||
|
||||
OWSAssertDebug(backgroundTask);
|
||||
backgroundTask = nil;
|
||||
}];
|
||||
|
||||
[downloadTask resume];
|
||||
}) retainUntilComplete];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -101,6 +101,39 @@ public class DotNetAPI : NSObject {
|
|||
}
|
||||
|
||||
// MARK: Public API
|
||||
@objc(downloadAttachmentFrom:)
|
||||
public static func objc_downloadAttachment(from url: String) -> AnyPromise {
|
||||
return AnyPromise.from(downloadAttachment(from: url))
|
||||
}
|
||||
|
||||
public static func downloadAttachment(from url: String) -> Promise<Data> {
|
||||
var error: NSError?
|
||||
var host = "https://\(URL(string: url)!.host!)"
|
||||
let sanitizedURL: String
|
||||
if FileServerAPI.fileStorageBucketURL.contains(host) {
|
||||
sanitizedURL = url.replacingOccurrences(of: FileServerAPI.fileStorageBucketURL, with: "\(FileServerAPI.server)/loki/v1")
|
||||
host = FileServerAPI.server
|
||||
} else {
|
||||
sanitizedURL = url.replacingOccurrences(of: host, with: "\(host)/loki/v1")
|
||||
}
|
||||
let request = AFHTTPRequestSerializer().request(withMethod: "GET", urlString: sanitizedURL, parameters: nil, error: &error)
|
||||
if let error = error {
|
||||
print("[Loki] Couldn't download attachment due to error: \(error).")
|
||||
return Promise(error: error)
|
||||
}
|
||||
let serverPublicKeyPromise = FileServerAPI.server.contains(host) ? Promise.value(FileServerAPI.fileServerPublicKey)
|
||||
: PublicChatAPI.getOpenGroupServerPublicKey(for: host)
|
||||
return serverPublicKeyPromise.then2 { serverPublicKey in
|
||||
return OnionRequestAPI.sendOnionRequest(request, to: host, using: serverPublicKey, isJSONRequired: false).map2 { json in
|
||||
guard let body = json["body"] as? JSON, let data = body["data"] as? [UInt8] else {
|
||||
print("[Loki] Couldn't parse attachment from: \(json).")
|
||||
throw DotNetAPIError.parsingFailed
|
||||
}
|
||||
return Data(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc(uploadAttachment:withID:toServer:)
|
||||
public static func objc_uploadAttachment(_ attachment: TSAttachmentStream, with attachmentID: String, to server: String) -> AnyPromise {
|
||||
return AnyPromise.from(uploadAttachment(attachment, with: attachmentID, to: server))
|
||||
|
|
|
@ -18,6 +18,7 @@ public final class FileServerAPI : DotNetAPI {
|
|||
public static let fileSizeORMultiplier: Double = 6
|
||||
|
||||
@objc public static let server = "https://file.getsession.org"
|
||||
@objc public static let fileStorageBucketURL = "https://file-static.lokinet.org"
|
||||
|
||||
// MARK: Storage
|
||||
override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" }
|
||||
|
|
|
@ -388,27 +388,11 @@ public final class PublicChatAPI : DotNetAPI {
|
|||
if oldProfilePictureURL != info.profilePictureURL || groupModel.groupImage == nil {
|
||||
storage.setProfilePictureURL(info.profilePictureURL, forPublicChatWithID: publicChatID, in: transaction)
|
||||
if let profilePictureURL = info.profilePictureURL {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
let manager = AFURLSessionManager.init(sessionConfiguration: configuration)
|
||||
let url = URL(string: "\(server)\(profilePictureURL)")!
|
||||
let request = URLRequest(url: url)
|
||||
let task = manager.downloadTask(with: request, progress: nil,
|
||||
destination: { (targetPath: URL, response: URLResponse) -> URL in
|
||||
let tempFilePath = URL(fileURLWithPath: OWSTemporaryDirectoryAccessibleAfterFirstAuth()).appendingPathComponent(UUID().uuidString)
|
||||
return tempFilePath
|
||||
},
|
||||
completionHandler: { (response: URLResponse, filePath: URL?, error: Error?) in
|
||||
if let error = error {
|
||||
print("[Loki] Couldn't download profile picture for public chat channel with ID: \(channel) on server: \(server).")
|
||||
return
|
||||
}
|
||||
if let filePath = filePath, let avatarData = try? Data.init(contentsOf: filePath) {
|
||||
let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(avatarData.count), sourceFilename: nil, caption: nil, albumMessageId: nil)
|
||||
try! attachmentStream.write(avatarData)
|
||||
groupThread.updateAvatar(with: attachmentStream)
|
||||
}
|
||||
})
|
||||
task.resume()
|
||||
FileServerAPI.downloadAttachment(from: "\(server)\(profilePictureURL)").map2 { data in
|
||||
let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(data.count), sourceFilename: nil, caption: nil, albumMessageId: nil)
|
||||
try attachmentStream.write(data)
|
||||
groupThread.updateAvatar(with: attachmentStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -500,12 +500,6 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
|||
OWSAssertDebug(job);
|
||||
TSAttachmentPointer *attachmentPointer = job.attachmentPointer;
|
||||
|
||||
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
|
||||
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);
|
||||
|
||||
// We want to avoid large downloads from a compromised or buggy service.
|
||||
const long kMaxDownloadSize = 10 * 1024 * 1024;
|
||||
__block BOOL hasCheckedContentLength = NO;
|
||||
|
@ -513,7 +507,6 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
|||
NSString *tempFilePath =
|
||||
[OWSTemporaryDirectoryAccessibleAfterFirstAuth() stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||
NSURL *tempFileURL = [NSURL fileURLWithPath:tempFilePath];
|
||||
|
||||
__block NSURLSessionDownloadTask *task;
|
||||
void (^failureHandler)(NSError *) = ^(NSError *error) {
|
||||
OWSLogError(@"Failed to download attachment with error: %@", error.description);
|
||||
|
@ -524,125 +517,27 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
|
|||
|
||||
failureHandlerParam(task, error);
|
||||
};
|
||||
|
||||
NSString *method = @"GET";
|
||||
NSError *serializationError = nil;
|
||||
NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:method
|
||||
URLString:location
|
||||
parameters:nil
|
||||
error:&serializationError];
|
||||
if (serializationError) {
|
||||
return failureHandler(serializationError);
|
||||
}
|
||||
|
||||
task = [manager downloadTaskWithRequest:request
|
||||
progress:^(NSProgress *progress) {
|
||||
OWSAssertDebug(progress != nil);
|
||||
|
||||
// Don't do anything until we've received at least one byte of data.
|
||||
if (progress.completedUnitCount < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
void (^abortDownload)(void) = ^{
|
||||
OWSFailDebug(@"Download aborted.");
|
||||
[task cancel];
|
||||
};
|
||||
|
||||
if (progress.totalUnitCount > kMaxDownloadSize || progress.completedUnitCount > kMaxDownloadSize) {
|
||||
// A malicious service might send a misleading content length header,
|
||||
// so....
|
||||
//
|
||||
// If the current downloaded bytes or the expected total byes
|
||||
// exceed the max download size, abort the download.
|
||||
OWSLogError(@"Attachment download exceed expected content length: %lld, %lld.",
|
||||
(long long)progress.totalUnitCount,
|
||||
(long long)progress.completedUnitCount);
|
||||
abortDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
job.progress = progress.fractionCompleted;
|
||||
|
||||
[self fireProgressNotification:MAX(kAttachmentDownloadProgressTheta, progress.fractionCompleted)
|
||||
attachmentId:attachmentPointer.uniqueId];
|
||||
|
||||
// We only need to check the content length header once.
|
||||
if (hasCheckedContentLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Once we've received some bytes of the download, check the content length
|
||||
// header for the download.
|
||||
//
|
||||
// If the task doesn't exist, or doesn't have a response, or is missing
|
||||
// the expected headers, or has an invalid or oversize content length, etc.,
|
||||
// abort the download.
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
|
||||
if (![httpResponse isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
OWSLogError(@"Attachment download has missing or invalid response.");
|
||||
abortDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary *headers = [httpResponse allHeaderFields];
|
||||
if (![headers isKindOfClass:[NSDictionary class]]) {
|
||||
OWSLogError(@"Attachment download invalid headers.");
|
||||
abortDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
NSString *contentLength = headers[@"Content-Length"];
|
||||
if (![contentLength isKindOfClass:[NSString class]]) {
|
||||
OWSLogError(@"Attachment download missing or invalid content length.");
|
||||
abortDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (contentLength.longLongValue > kMaxDownloadSize) {
|
||||
OWSLogError(@"Attachment download content length exceeds max download size.");
|
||||
abortDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
// This response has a valid content length that is less
|
||||
// than our max download size. Proceed with the download.
|
||||
hasCheckedContentLength = YES;
|
||||
}
|
||||
destination:^(NSURL *targetPath, NSURLResponse *response) {
|
||||
return tempFileURL;
|
||||
}
|
||||
completionHandler:^(NSURLResponse *response, NSURL *_Nullable filePath, NSError *_Nullable error) {
|
||||
if (error) {
|
||||
failureHandler(error);
|
||||
return;
|
||||
}
|
||||
if (![tempFileURL isEqual:filePath]) {
|
||||
OWSLogError(@"Unexpected temp file path.");
|
||||
NSError *error = OWSErrorWithCodeDescription(
|
||||
OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:tempFilePath];
|
||||
if (!fileSize) {
|
||||
OWSLogError(@"Could not determine attachment file size.");
|
||||
NSError *error = OWSErrorWithCodeDescription(
|
||||
OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
|
||||
return failureHandler(error);
|
||||
}
|
||||
if (fileSize.unsignedIntegerValue > kMaxDownloadSize) {
|
||||
OWSLogError(@"Attachment download length exceeds max size.");
|
||||
NSError *error = OWSErrorWithCodeDescription(
|
||||
OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
|
||||
return failureHandler(error);
|
||||
}
|
||||
|
||||
[[LKFileServerAPI downloadAttachmentFrom:location].then(^(NSData *data) {
|
||||
BOOL success = [data writeToFile:tempFilePath atomically:YES];
|
||||
if (success) {
|
||||
successHandler(tempFilePath);
|
||||
}];
|
||||
}
|
||||
|
||||
[task resume];
|
||||
NSNumber *_Nullable fileSize = [OWSFileSystem fileSizeOfPath:tempFilePath];
|
||||
if (!fileSize) {
|
||||
OWSLogError(@"Could not determine attachment file size.");
|
||||
NSError *error = OWSErrorWithCodeDescription(
|
||||
OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
|
||||
return failureHandler(error);
|
||||
}
|
||||
if (fileSize.unsignedIntegerValue > kMaxDownloadSize) {
|
||||
OWSLogError(@"Attachment download length exceeds max size.");
|
||||
NSError *error = OWSErrorWithCodeDescription(
|
||||
OWSErrorCodeInvalidMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
|
||||
return failureHandler(error);
|
||||
}
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
|
||||
- (void)fireProgressNotification:(CGFloat)progress attachmentId:(NSString *)attachmentId
|
||||
|
|
Loading…
Reference in a new issue