Don't locally enforce a max backup upload size limit

This commit is contained in:
Harry 2025-07-17 13:51:28 -07:00 committed by GitHub
parent f23120e787
commit 92e5cca0f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 87 additions and 12 deletions

View File

@ -200,7 +200,21 @@ public class BackupArchiveManagerImpl: BackupArchiveManager {
localAci: localIdentifiers.aci,
auth: auth
)
let form = try await backupRequestManager.fetchBackupUploadForm(auth: backupAuth)
let form: Upload.Form
do {
form = try await backupRequestManager.fetchBackupUploadForm(
backupByteLength: metadata.encryptedDataLength,
auth: backupAuth
)
} catch let error {
switch (error as? BackupArchive.Response.BackupUploadFormError) {
case .tooLarge:
Logger.warn("Backup too large! \(metadata.encryptedDataLength)")
default:
break
}
throw error
}
let result = try await attachmentUploadManager.uploadBackup(
localUploadMetadata: metadata,
form: form,

View File

@ -391,6 +391,7 @@ private class BackupRequestManagerMock: BackupRequestManager {
}
func fetchBackupUploadForm(
backupByteLength: UInt32,
auth: SignalServiceKit.BackupServiceAuth
) async throws -> SignalServiceKit.Upload.Form {
fatalError("Unimplemented")

View File

@ -76,6 +76,15 @@ public extension BackupArchive {
case outOfCapacity = 413
case rateLimited = 429
}
public enum BackupUploadFormError: Int, Error {
case badArgument = 400
case invalidAuth = 401
case forbidden = 403
/// The backup file is too large (as reported by us in `backupByteLength`.
case tooLarge = 413
case rateLimited = 429
}
}
}
@ -95,7 +104,11 @@ public protocol BackupRequestManager {
forceRefreshUnlessCachedPaidCredential: Bool
) async throws -> BackupServiceAuth
func fetchBackupUploadForm(auth: BackupServiceAuth) async throws -> Upload.Form
/// - parameter backupByteLength: length in bytes of the encrypted backup file we will upload
func fetchBackupUploadForm(
backupByteLength: UInt32,
auth: BackupServiceAuth
) async throws -> Upload.Form
func fetchBackupMediaAttachmentUploadForm(auth: BackupServiceAuth) async throws -> Upload.Form
@ -203,12 +216,31 @@ public struct BackupRequestManagerImpl: BackupRequestManager {
// MARK: - Upload Forms
/// CDN upload form for uploading a backup
public func fetchBackupUploadForm(auth: BackupServiceAuth) async throws -> Upload.Form {
public func fetchBackupUploadForm(
backupByteLength: UInt32,
auth: BackupServiceAuth
) async throws -> Upload.Form {
owsAssertDebug(auth.type == .messages)
return try await executeBackupService(
auth: auth,
requestFactory: OWSRequestFactory.backupUploadFormRequest(auth:)
)
do {
return try await executeBackupService(
auth: auth,
requestFactory: { auth in
OWSRequestFactory.backupUploadFormRequest(
backupByteLength: backupByteLength,
auth: auth
)
}
)
} catch let error {
if
let httpStatusCode = error.httpStatusCode,
let error = BackupArchive.Response.BackupUploadFormError(rawValue: httpStatusCode)
{
throw error
} else {
throw error
}
}
}
/// CDN upload form for uploading backup media

View File

@ -20,9 +20,15 @@ extension OWSRequestFactory {
return request
}
public static func backupUploadFormRequest(auth: BackupServiceAuth) -> TSRequest {
/// - parameter backupByteLength: length in bytes of the encrypted backup file we will upload
public static func backupUploadFormRequest(
backupByteLength: UInt32,
auth: BackupServiceAuth
) -> TSRequest {
var urlComps = URLComponents(string: "v1/archives/upload/form")!
urlComps.queryItems = [URLQueryItem(name: "uploadLength", value: "\(backupByteLength)")]
var request = TSRequest(
url: URL(string: "v1/archives/upload/form")!,
url: urlComps.url!,
method: "GET",
parameters: nil
)

View File

@ -124,6 +124,11 @@ public enum Upload {
/// Does NOT take into account current backup plan state; just per-attachment
/// backup eligibility.
public let attachmentByteSize: UInt64
/// We don't enforce a size limit locally for backups; we let the server
/// enforce the limit and fail the upload if we surpass it.
public static var maxUploadSizeBytes: UInt { .max }
public static var maxPlaintextSizeBytes: UInt { .max }
}
public struct LocalUploadMetadata: AttachmentUploadMetadata, Codable {
@ -143,6 +148,9 @@ public enum Upload {
public let plaintextDataLength: UInt32
public var isReusedTransitTierUpload: Bool { false }
public static var maxUploadSizeBytes: UInt { OWSMediaUtils.kMaxAttachmentUploadSizeBytes }
public static var maxPlaintextSizeBytes: UInt { OWSMediaUtils.kMaxFileSizeGeneric }
}
public struct LinkNSyncUploadMetadata: UploadMetadata {
@ -150,6 +158,11 @@ public enum Upload {
public let fileUrl: URL
/// The length of the file.
public let encryptedDataLength: UInt32
/// We don't enforce a size limit locally for backups; we let the server
/// enforce the limit and fail the upload if we surpass it.
public static var maxUploadSizeBytes: UInt { .max }
public static var maxPlaintextSizeBytes: UInt { .max }
}
public struct ReusedUploadMetadata: AttachmentUploadMetadata {
@ -170,6 +183,9 @@ public enum Upload {
public let encryptedDataLength: UInt32
public var isReusedTransitTierUpload: Bool { false }
public static var maxUploadSizeBytes: UInt { OWSMediaUtils.kMaxAttachmentUploadSizeBytes }
public static var maxPlaintextSizeBytes: UInt { OWSMediaUtils.kMaxFileSizeGeneric }
}
public struct Result<Metadata: UploadMetadata> {
@ -234,8 +250,8 @@ extension Upload.LocalUploadMetadata {
let plaintextLength = UInt32(plaintextLengthRaw)
guard
plaintextLength <= OWSMediaUtils.kMaxFileSizeGeneric,
length <= OWSMediaUtils.kMaxAttachmentUploadSizeBytes
plaintextLength <= Self.maxPlaintextSizeBytes,
length <= Self.maxUploadSizeBytes
else {
throw OWSAssertionError("Data is too large: \(length).")
}

View File

@ -8,6 +8,9 @@ import Foundation
/// Applies to attachment uploads, backup proto uploads, etc.
public protocol UploadMetadata {
var encryptedDataLength: UInt32 { get }
static var maxUploadSizeBytes: UInt { get }
static var maxPlaintextSizeBytes: UInt { get }
}
/// Includes extra info like digest for validation.

View File

@ -130,7 +130,10 @@ class _AttachmentUploadManager_BackupRequestManagerMock: BackupRequestManager {
func registerBackupKeys(localAci: Aci, auth: ChatServiceAuth) async throws {}
func fetchBackupUploadForm(auth: BackupServiceAuth) async throws -> Upload.Form {
func fetchBackupUploadForm(
backupByteLength: UInt32,
auth: BackupServiceAuth
) async throws -> Upload.Form {
fatalError("Unimplemented for tests")
}