Merge pull request #58 from loki-project/attachments

Attachments
pull/59/head
gmbnt 6 years ago committed by GitHub
commit 736f50d5d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -28,7 +28,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "51E5126436E74C817013DE9DE3C88131"
BlueprintIdentifier = "A16588799C7A0AB3A5ACEF8339CCB8BC"
BuildableName = "SignalServiceKit.framework"
BlueprintName = "SignalServiceKit"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
@ -140,8 +140,6 @@
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
stopOnEveryThreadSanitizerIssue = "YES"
stopOnEveryUBSanitizerIssue = "YES"
migratedStopOnEveryIssue = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"

@ -232,7 +232,7 @@ const CGFloat kMaxTextViewHeight = 98;
// H Stack
_hStack = [[UIStackView alloc]
initWithArrangedSubviews:@[ /*self.attachmentButton,*/ vStackWrapper, /*self.voiceMemoButton,*/ self.sendButton ]];
initWithArrangedSubviews:@[ self.attachmentButton, vStackWrapper, /*self.voiceMemoButton,*/ self.sendButton ]];
self.hStack.axis = UILayoutConstraintAxisHorizontal;
self.hStack.layoutMarginsRelativeArrangement = YES;
self.hStack.layoutMargins = UIEdgeInsetsMake(6, 6, 6, 6);

@ -3032,7 +3032,7 @@ typedef enum : NSUInteger {
[self dismissViewControllerAnimated:YES
completion:^{
OWSAssertDebug(self.isFirstResponder);
// OWSAssertDebug(self.isFirstResponder);
if (@available(iOS 10, *)) {
// do nothing
} else {
@ -3509,6 +3509,7 @@ typedef enum : NSUInteger {
[gifAction setValue:gifImage forKey:@"image"];
[actionSheet addAction:gifAction];
/*
UIAlertAction *chooseDocumentAction =
[UIAlertAction actionWithTitle:NSLocalizedString(@"MEDIA_FROM_DOCUMENT_PICKER_BUTTON",
@"action sheet button title when choosing attachment type")
@ -3536,6 +3537,7 @@ typedef enum : NSUInteger {
[chooseContactAction setValue:chooseContactImage forKey:@"image"];
[actionSheet addAction:chooseContactAction];
}
*/
[self dismissKeyBoard];
[self presentAlert:actionSheet];

@ -192,13 +192,13 @@ class SendMediaNavigationController: OWSNavigationController {
// MARK: State
private var attachmentDraftCollection: AttachmentDraftCollection = .empty
private lazy var attachmentDraftCollection = AttachmentDraftCollection.empty // Lazy to avoid https://bugs.swift.org/browse/SR-6657
private var attachments: [SignalAttachment] {
return attachmentDraftCollection.attachmentDrafts.map { $0.attachment }
}
private let mediaLibrarySelections: OrderedDictionary<PHAsset, MediaLibrarySelection> = OrderedDictionary()
private lazy var mediaLibrarySelections = OrderedDictionary<PHAsset, MediaLibrarySelection>() // Lazy to avoid https://bugs.swift.org/browse/SR-6657
// MARK: Child VC's
@ -463,13 +463,17 @@ private extension AttachmentDraft {
}
}
private struct AttachmentDraftCollection {
private(set) var attachmentDrafts: [AttachmentDraft]
private final class AttachmentDraftCollection {
lazy var attachmentDrafts = [AttachmentDraft]() // Lazy to avoid https://bugs.swift.org/browse/SR-6657
static var empty: AttachmentDraftCollection {
return AttachmentDraftCollection(attachmentDrafts: [])
}
init(attachmentDrafts: [AttachmentDraft]) {
self.attachmentDrafts = attachmentDrafts
}
// MARK: -
var count: Int {
@ -498,15 +502,15 @@ private struct AttachmentDraftCollection {
}
}
mutating func append(_ element: AttachmentDraft) {
func append(_ element: AttachmentDraft) {
attachmentDrafts.append(element)
}
mutating func remove(attachment: SignalAttachment) {
attachmentDrafts = attachmentDrafts.filter { $0.attachment != attachment }
func remove(attachment: SignalAttachment) {
attachmentDrafts.removeAll { $0.attachment == attachment }
}
mutating func selectedFromPicker(attachments: [MediaLibraryAttachment]) {
func selectedFromPicker(attachments: [MediaLibraryAttachment]) {
let pickedAttachments: Set<MediaLibraryAttachment> = Set(attachments)
let oldPickerAttachments: Set<MediaLibraryAttachment> = Set(self.pickerAttachments)

@ -378,6 +378,7 @@ message AttachmentPointer {
optional uint32 width = 9;
optional uint32 height = 10;
optional string caption = 11;
optional string url = 101; // Loki
}
message GroupContext {

@ -77,7 +77,7 @@ public extension LokiAPI {
}.recover { error -> Promise<LokiAPITarget> in
Analytics.shared.track("Seed Node Failed")
throw error
}
}.retryingIfNeeded(maxRetryCount: 16) // The seed nodes have historically been unreliable
} else {
return Promise<LokiAPITarget> { seal in
seal.fulfill(randomSnodePool.randomElement()!)

@ -9,7 +9,7 @@ public class LokiDotNetAPI : NSObject {
// MARK: Error
public enum Error : Swift.Error {
case parsingFailed, decryptionFailed, signingFailed
case generic, parsingFailed, encryptionFailed, decryptionFailed, signingFailed
}
// MARK: Database

@ -10,39 +10,12 @@ public final class LokiStorageAPI : LokiDotNetAPI {
private static let server = "https://file.lokinet.org"
// #endif
private static let deviceLinkType = "network.loki.messenger.devicemapping"
private static let attachmentType = "network.loki"
// MARK: Database
override internal class var authTokenCollection: String { return "LokiStorageAuthTokenCollection" }
// MARK: Public API
/// Adds the given device link to the user's device mapping on the server.
public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.insert(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.addDeviceLink(deviceLink, in: transaction)
}
}
}
/// Removes the given device link from the user's device mapping on the server.
public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.remove(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.removeDeviceLink(deviceLink, in: transaction)
}
}
}
// MARK: Device Links (Public API)
/// Gets the device links associated with the given hex encoded public key from the
/// server and stores and returns the valid ones.
public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> Promise<Set<DeviceLink>> {
@ -99,14 +72,7 @@ public final class LokiStorageAPI : LokiDotNetAPI {
}
}
}
// MARK: Public API (Obj-C)
@objc(getDeviceLinksAssociatedWith:)
public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> AnyPromise {
return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKey))
}
// MARK: Private API
public static func setDeviceLinks(_ deviceLinks: Set<DeviceLink>) -> Promise<Void> {
print("[Loki] Updating device links.")
return getAuthToken(for: server).then { token -> Promise<Void> in
@ -124,4 +90,112 @@ public final class LokiStorageAPI : LokiDotNetAPI {
}
}
}
/// Adds the given device link to the user's device mapping on the server.
public static func addDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.insert(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.addDeviceLink(deviceLink, in: transaction)
}
}
}
/// Removes the given device link from the user's device mapping on the server.
public static func removeDeviceLink(_ deviceLink: DeviceLink) -> Promise<Void> {
var deviceLinks: Set<DeviceLink> = []
storage.dbReadConnection.read { transaction in
deviceLinks = storage.getDeviceLinks(for: userHexEncodedPublicKey, in: transaction)
}
deviceLinks.remove(deviceLink)
return setDeviceLinks(deviceLinks).map {
storage.dbReadWriteConnection.readWrite { transaction in
storage.removeDeviceLink(deviceLink, in: transaction)
}
}
}
// MARK: Device Links (Public Obj-C API)
@objc(getDeviceLinksAssociatedWith:)
public static func objc_getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> AnyPromise {
return AnyPromise.from(getDeviceLinks(associatedWith: hexEncodedPublicKey))
}
// MARK: Attachments (Public API)
public static func uploadAttachment(_ attachment: TSAttachmentStream, attachmentID: String) -> Promise<Void> {
return Promise<Void>() { seal in
getAuthToken(for: server).done { token in
// Encrypt the attachment
guard let unencryptedAttachmentData = try? attachment.readDataFromFile() else {
print("[Loki] Couldn't read attachment data from disk.")
return seal.reject(Error.generic)
}
var encryptionKey = NSData()
var digest = NSData()
guard let encryptedAttachmentData = Cryptography.encryptAttachmentData(unencryptedAttachmentData, outKey: &encryptionKey, outDigest: &digest) else {
print("[Loki] Couldn't encrypt attachment.")
return seal.reject(Error.encryptionFailed)
}
attachment.encryptionKey = encryptionKey as Data
attachment.digest = digest as Data
// Create the request
let url = "\(server)/files"
let parameters: JSON = [ "type" : attachmentType, "Content-Type" : "application/binary" ]
var error: NSError?
var request = AFHTTPRequestSerializer().multipartFormRequest(withMethod: "POST", urlString: url, parameters: parameters, constructingBodyWith: { formData in
formData.appendPart(withFileData: encryptedAttachmentData, name: "content", fileName: UUID().uuidString, mimeType: "application/binary")
}, error: &error)
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
if let error = error {
print("[Loki] Couldn't upload attachment due to error: \(error).")
throw error
}
// Send the request
let task = AFURLSessionManager(sessionConfiguration: .default).uploadTask(withStreamedRequest: request as URLRequest, progress: { rawProgress in
// Broadcast progress updates
let progress = max(0.1, rawProgress.fractionCompleted)
let userInfo: [String:Any] = [ kAttachmentUploadProgressKey : progress, kAttachmentUploadAttachmentIDKey : attachmentID ]
DispatchQueue.main.async {
NotificationCenter.default.post(name: .attachmentUploadProgress, object: nil, userInfo: userInfo)
}
}, completionHandler: { response, responseObject, error in
if let error = error {
print("[Loki] Couldn't upload attachment due to error: \(error).")
return seal.reject(error)
}
let statusCode = (response as! HTTPURLResponse).statusCode
let isSuccessful = (200...299) ~= statusCode
guard isSuccessful else {
print("[Loki] Couldn't upload attachment.")
return seal.reject(Error.generic)
}
// Parse the server ID & download URL
guard let json = responseObject as? JSON, let data = json["data"] as? JSON, let serverID = data["id"] as? UInt64, let downloadURL = data["url"] as? String else {
print("[Loki] Couldn't parse attachment from: \(responseObject).")
return seal.reject(Error.parsingFailed)
}
// Update the attachment
attachment.serverId = serverID
attachment.isUploaded = true
attachment.downloadURL = downloadURL
attachment.save()
return seal.fulfill(())
})
task.resume()
}.catch { error in
print("[Loki] Couldn't upload attachment.")
seal.reject(error)
}
}
}
// MARK: Attachments (Public Obj-C API)
@objc(uploadAttachment:withID:)
public static func objc_uploadAttachment(_ attachment: TSAttachmentStream, attachmentID: String) -> AnyPromise {
return AnyPromise.from(uploadAttachment(attachment, attachmentID: attachmentID))
}
}

@ -370,66 +370,19 @@ 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);
}
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);
dispatch_async([OWSDispatch attachmentsQueue], ^{
[self downloadFromLocation:attachmentPointer.downloadURL
job:job
success:^(NSString *encryptedDataFilePath) {
[self decryptAttachmentPath:encryptedDataFilePath
attachmentPointer:attachmentPointer
success:markAndHandleSuccess
failure:markAndHandleFailure];
}
return markAndHandleFailure(error);
}];
failure:^(NSURLSessionTask *_Nullable task, NSError *error) {
markAndHandleFailure(error);
}];
});
}
- (void)decryptAttachmentPath:(NSString *)encryptedDataFilePath
@ -530,7 +483,7 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
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 = 150 * 1024 * 1024;
const long kMaxDownloadSize = 12 * 1024 * 1024;
__block BOOL hasCheckedContentLength = NO;
NSString *tempFilePath =

@ -32,6 +32,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (nonatomic, readonly) NSString *contentType;
@property (atomic, readwrite) BOOL isDownloaded;
@property (nonatomic) TSAttachmentType attachmentType;
@property (nonatomic) NSString *downloadURL;
// Though now required, may incorrectly be 0 on legacy attachments.
@property (nonatomic, readonly) UInt32 byteCount;

@ -167,6 +167,9 @@ NS_ASSUME_NONNULL_BEGIN
albumMessageId:albumMessageId
attachmentType:attachmentType
mediaSize:mediaSize];
pointer.downloadURL = attachmentProto.url; // Loki
return pointer;
}

@ -869,6 +869,8 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
}
}
}
builder.url = self.downloadURL;
NSError *error;
SSKProtoAttachmentPointer *_Nullable attachmentProto = [builder buildAndReturnError:&error];

@ -15,6 +15,7 @@
#import "TSNetworkManager.h"
#import <SignalCoreKit/Cryptography.h>
#import <YapDatabase/YapDatabaseConnection.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -82,31 +83,13 @@ 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();
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];
}];
[[LKStorageAPI uploadAttachment:attachmentStream withID:self.attachmentId]
.thenOn(dispatch_get_main_queue(), ^() {
[self reportSuccess];
})
.catchOn(dispatch_get_main_queue(), ^(NSError *error) {
[self reportError:error];
}) retainUntilComplete];
}
- (void)uploadWithServerId:(UInt64)serverId

@ -384,7 +384,7 @@ dispatch_queue_t NetworkManagerQueue()
{
OWSAssertDebug(failureBlock);
OWSAssertDebug(request);
OWSAssertDebug(task);
// OWSAssertDebug(task);
OWSAssertDebug(networkError);
NSInteger statusCode = [task statusCode];

@ -5378,6 +5378,9 @@ extension SSKProtoSyncMessage.SSKProtoSyncMessageBuilder {
if let _value = caption {
builder.setCaption(_value)
}
if let _value = url {
builder.setUrl(_value)
}
return builder
}
@ -5437,6 +5440,10 @@ extension SSKProtoSyncMessage.SSKProtoSyncMessageBuilder {
proto.caption = valueParam
}
@objc public func setUrl(_ valueParam: String) {
proto.url = valueParam
}
@objc public func build() throws -> SSKProtoAttachmentPointer {
return try SSKProtoAttachmentPointer.parseProto(proto)
}
@ -5538,6 +5545,16 @@ extension SSKProtoSyncMessage.SSKProtoSyncMessageBuilder {
return proto.hasCaption
}
@objc public var url: String? {
guard proto.hasURL else {
return nil
}
return proto.url
}
@objc public var hasURL: Bool {
return proto.hasURL
}
private init(proto: SignalServiceProtos_AttachmentPointer,
id: UInt64) {
self.proto = proto

@ -2200,6 +2200,16 @@ struct SignalServiceProtos_AttachmentPointer {
/// Clears the value of `caption`. Subsequent reads from it will return its default value.
mutating func clearCaption() {self._caption = nil}
/// Loki
var url: String {
get {return _url ?? String()}
set {_url = newValue}
}
/// Returns true if `url` has been explicitly set.
var hasURL: Bool {return self._url != nil}
/// Clears the value of `url`. Subsequent reads from it will return its default value.
mutating func clearURL() {self._url = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
enum Flags: SwiftProtobuf.Enum {
@ -2238,6 +2248,7 @@ struct SignalServiceProtos_AttachmentPointer {
fileprivate var _width: UInt32? = nil
fileprivate var _height: UInt32? = nil
fileprivate var _caption: String? = nil
fileprivate var _url: String? = nil
}
#if swift(>=4.2)
@ -4811,6 +4822,7 @@ extension SignalServiceProtos_AttachmentPointer: SwiftProtobuf.Message, SwiftPro
9: .same(proto: "width"),
10: .same(proto: "height"),
11: .same(proto: "caption"),
101: .same(proto: "url"),
]
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@ -4827,6 +4839,7 @@ extension SignalServiceProtos_AttachmentPointer: SwiftProtobuf.Message, SwiftPro
case 9: try decoder.decodeSingularUInt32Field(value: &self._width)
case 10: try decoder.decodeSingularUInt32Field(value: &self._height)
case 11: try decoder.decodeSingularStringField(value: &self._caption)
case 101: try decoder.decodeSingularStringField(value: &self._url)
default: break
}
}
@ -4866,6 +4879,9 @@ extension SignalServiceProtos_AttachmentPointer: SwiftProtobuf.Message, SwiftPro
if let v = self._caption {
try visitor.visitSingularStringField(value: v, fieldNumber: 11)
}
if let v = self._url {
try visitor.visitSingularStringField(value: v, fieldNumber: 101)
}
try unknownFields.traverse(visitor: &visitor)
}
@ -4881,6 +4897,7 @@ extension SignalServiceProtos_AttachmentPointer: SwiftProtobuf.Message, SwiftPro
if lhs._width != rhs._width {return false}
if lhs._height != rhs._height {return false}
if lhs._caption != rhs._caption {return false}
if lhs._url != rhs._url {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}

@ -22,7 +22,7 @@ static void *kNSError_MessageSender_IsFatal = &kNSError_MessageSender_IsFatal;
NSNumber *value = objc_getAssociatedObject(self, kNSError_MessageSender_IsRetryable);
// This value should always be set for all errors by the time OWSSendMessageOperation
// queries it's value. If not, default to retrying in production.
OWSAssertDebug(value);
// OWSAssertDebug(value);
return value ? [value boolValue] : YES;
}

Loading…
Cancel
Save