Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Upload/UploadV2.swift
1 views
//
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation

extension Upload {
    public enum CDN0 {
        public struct Form: Codable {
            let acl: String
            let key: String
            let policy: String
            let algorithm: String
            let credential: String
            let date: String
            let signature: String
            let attachmentId: UInt64?
            let attachmentIdString: String?
        }
    }
}

extension Upload.CDN0.Form {
    public enum ParsingError: Error {
        case missingField(String)
    }

    public static func parse(proto: GroupsProtoAvatarUploadAttributes) throws -> Self {
        guard let acl = proto.acl else { throw ParsingError.missingField("acl") }
        guard let key = proto.key else { throw ParsingError.missingField("key") }
        guard let policy = proto.policy else { throw ParsingError.missingField("policy") }
        guard let algorithm = proto.algorithm else { throw ParsingError.missingField("algorithm") }
        guard let credential = proto.credential else { throw ParsingError.missingField("credential") }
        guard let date = proto.date else { throw ParsingError.missingField("date") }
        guard let signature = proto.signature else { throw ParsingError.missingField("signature") }

        return .init(
            acl: acl,
            key: key,
            policy: policy,
            algorithm: algorithm,
            credential: credential,
            date: date,
            signature: signature,
            attachmentId: nil,
            attachmentIdString: nil,
        )
    }
}

extension Upload.CDN0 {
    public static func upload(data: Data, uploadForm: Upload.CDN0.Form) async throws -> String {
        if DependenciesBridge.shared.appExpiry.isExpired(now: Date()) {
            throw AppExpiredError()
        }

        let dataFileUrl = OWSFileSystem.temporaryFileUrl(
            fileExtension: nil,
            isAvailableWhileDeviceLocked: true,
        )
        try data.write(to: dataFileUrl)

        let cdn0UrlSession = await SSKEnvironment.shared.signalServiceRef.sharedUrlSessionForCdn(cdnNumber: 0, maxResponseSize: nil)
        // urlPath is "" for all endpoints that still use CDN0
        let request = try cdn0UrlSession.endpoint.buildRequest("", method: .post)

        // We have to build up the form manually vs. simply passing in a parameters 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.
        var textParts = uploadForm.asOrderedDictionary
        textParts.append(key: "Content-Type", value: MimeType.applicationOctetStream.rawValue)

        do {
            _ = try await cdn0UrlSession.performMultiPartUpload(
                request: request,
                fileUrl: dataFileUrl,
                name: "file",
                fileName: "file",
                mimeType: MimeType.applicationOctetStream.rawValue,
                textParts: textParts,
            )
        } catch {
            Logger.warn("\(error)")
            throw error
        }

        return uploadForm.key
    }
}

// See: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
private extension Upload.CDN0.Form {
    var asOrderedDictionary: OrderedDictionary<String, String> {
        // We have to build up the form manually vs. simply passing in a parameters dict
        // because AWS is sensitive to the order of the form params (at least the "key"
        // field must occur early on).
        var result = OrderedDictionary<String, String>()

        // For consistency, all fields are ordered here in a known working order.
        result.append(key: "key", value: self.key)
        result.append(key: "acl", value: self.acl)
        result.append(key: "x-amz-algorithm", value: self.algorithm)
        result.append(key: "x-amz-credential", value: self.credential)
        result.append(key: "x-amz-date", value: self.date)
        result.append(key: "policy", value: self.policy)
        result.append(key: "x-amz-signature", value: self.signature)
        return result
    }

}