From 951c3a8e563b9aeba37ebe2c5ed9d22f4e88287a Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 17 Dec 2024 19:04:32 -0500 Subject: [PATCH 01/14] Update MultipartRequest - Replace NSMutableData operation with streaming Data to a temporary file - Use httpBodyStream to stream file content directly into the request body - Use FileHandle to manage file writing - Calculate file size after writing to ensure accurate HTTP header - defer file handling --- .../Features/Network/MultipartRequest.swift | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/Sources/Features/Network/MultipartRequest.swift b/Sources/Features/Network/MultipartRequest.swift index 2206dad1..bbca2fc7 100644 --- a/Sources/Features/Network/MultipartRequest.swift +++ b/Sources/Features/Network/MultipartRequest.swift @@ -48,41 +48,59 @@ extension MultipartRequest { var multipartRequest = urlRequest let boundary = generateBoundaryString() multipartRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - - let boundaryPrefix = "--\(boundary)\r\n" - let body = NSMutableData() + + // temporary file to stream data + let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + FileManager.default.createFile(atPath: tempURL.path, contents: nil, attributes: nil) + let fileHandle = try FileHandle(forWritingTo: tempURL) + + defer { fileHandle.closeFile() } + // attributes for attribute in report.attributes { - body.appendString(boundaryPrefix) - body.appendString("Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n") - body.appendString("\(attribute.value)\r\n") + writeToFile(fileHandle, "--\(boundary)\r\n") + writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n") + writeToFile(fileHandle, "\(attribute.value)\r\n") } - // report file - body.appendString(boundaryPrefix) - body.appendString("Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n") - body.appendString("Content-Type: application/octet-stream\r\n\r\n") - body.append(report.reportData) - body.appendString("\r\n") - + + // report + writeToFile(fileHandle, "--\(boundary)\r\n") + writeToFile(fileHandle, "Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n") + writeToFile(fileHandle, "Content-Type: application/octet-stream\r\n\r\n") + fileHandle.write(report.reportData) + writeToFile(fileHandle, "\r\n") + // attachments for attachment in Set(report.attachmentPaths).compactMap(Attachment.init(filePath:)) { - body.appendString(boundaryPrefix) - body.appendString("Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") - body.appendString("Content-Type: \(attachment.mimeType)\r\n\r\n") - body.append(attachment.data) - body.appendString("\r\n") + writeToFile(fileHandle, "--\(boundary)\r\n") + writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") + writeToFile(fileHandle, "Content-Type: \(attachment.mimeType)\r\n\r\n") + fileHandle.write(attachment.data) + writeToFile(fileHandle, "\r\n") } - body.appendString("\(boundaryPrefix)--") - multipartRequest.httpBody = body as Data - - multipartRequest.setValue("\(body.length)", forHTTPHeaderField: "Content-Length") - + + // Final boundary + writeToFile(fileHandle, "--\(boundary)--\r\n") + + // Set Content-Length + let fileSize = try FileManager.default.attributesOfItem(atPath: tempURL.path)[.size] as? Int ?? 0 + multipartRequest.setValue("\(fileSize)", forHTTPHeaderField: "Content-Length") + + // Attach file stream to HTTP body + multipartRequest.httpBodyStream = InputStream(url: tempURL) + return multipartRequest } private static func generateBoundaryString() -> String { return "Boundary-\(NSUUID().uuidString)" } + + private static func writeToFile(_ fileHandle: FileHandle, _ string: String) { + if let data = string.data(using: .utf8) { + fileHandle.write(data) + } + } } private extension NSMutableData { From b422ceade0639ecb29d4affe10c2269ff75f0f75 Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 17 Dec 2024 19:05:46 -0500 Subject: [PATCH 02/14] Update Attachment - Migrate to modern UniformTypeIdentifiers API, keep legacy support - Cleanup --- .../Repository/Model/Attachment.swift | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/Sources/Features/Repository/Model/Attachment.swift b/Sources/Features/Repository/Model/Attachment.swift index 2cd120f2..12fb2b1f 100644 --- a/Sources/Features/Repository/Model/Attachment.swift +++ b/Sources/Features/Repository/Model/Attachment.swift @@ -1,4 +1,5 @@ import Foundation +import UniformTypeIdentifiers #if (os(iOS) || os(tvOS)) import MobileCoreServices #elseif os(macOS) @@ -7,42 +8,58 @@ import CoreServices struct Attachment { let data: Data - // file name with extension that represents real file name + /// File name with extension that represents the real file name let filename: String + /// MIME type of the file let mimeType: String - - // Make sure attachments are not bigger than 10 MB. + + /// Maximum allowed size for attachments (10 MB) private static let maximumAttachmentSize = 10 * 1024 * 1024 - + + /// Custom prefix for generated filenames + private static let filenamePrefix = "attachment_" + + /// Initializes an Attachment from a file path + /// - Parameter filePath: Path to the file init?(filePath: String) { let fileURL = URL(fileURLWithPath: filePath) - - // Don't allow too large attachments + + // Ensure file size is within maximumAttachmentSize limits guard let fileAttributes = try? FileManager.default.attributesOfItem(atPath: filePath), let fileSize = fileAttributes[FileAttributeKey.size] as? UInt64, - fileSize < Attachment.maximumAttachmentSize else { + fileSize <= Attachment.maximumAttachmentSize else { BacktraceLogger.warning("Skipping attachment because fileSize couldn't be read or is larger than 10MB: \(filePath)") return nil } - + do { - data = try Data(contentsOf: fileURL, options: Data.ReadingOptions.mappedIfSafe) + data = try Data(contentsOf: fileURL, options: .mappedIfSafe) } catch { BacktraceLogger.warning("Skipping attachment: \(filePath): \(error.localizedDescription)") return nil } - + mimeType = Attachment.mimeTypeForPath(fileUrl: fileURL) - filename = "attachment_" + fileURL.lastPathComponent + filename = Attachment.filenamePrefix + fileURL.lastPathComponent } - + + /// Determines the MIME type for a file URL + /// - Parameter fileUrl: File URL + /// - Returns: MIME type as a string, or "application/octet-stream" as fallback static private func mimeTypeForPath(fileUrl: URL) -> String { - guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, - fileUrl.pathExtension as NSString, nil)? - .takeRetainedValue(), - let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)? + if #available(iOS 14.0, *) { + if let type = UTType(filenameExtension: fileUrl.pathExtension)?.preferredMIMEType { + return type + } + } else { + guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, + fileUrl.pathExtension as NSString, nil)? + .takeRetainedValue(), + let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)? .takeRetainedValue() as String? else { return "application/octet-stream" } - return mimetype + return mimetype + } + return "application/octet-stream" } } From ec3f88841901578f235e36e7a74ce8b5e2f0f02d Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 17 Dec 2024 19:06:46 -0500 Subject: [PATCH 03/14] ExampleApp testing sample code --- Examples/Example-iOS-ObjC/ViewController.m | 272 ++++++++++++++++++++- 1 file changed, 263 insertions(+), 9 deletions(-) diff --git a/Examples/Example-iOS-ObjC/ViewController.m b/Examples/Example-iOS-ObjC/ViewController.m index 798c7961..b2f83ac7 100644 --- a/Examples/Example-iOS-ObjC/ViewController.m +++ b/Examples/Example-iOS-ObjC/ViewController.m @@ -18,19 +18,182 @@ - (void)viewDidLoad { - (IBAction) outOfMemoryReportAction: (id) sender { // The trick is: to aggressively take up memory but not allocate a block too large to cause a crash // This is obviously device dependent, so the 500k may have to be tweaked - int size = 500000; - for (int i = 0; i < 10000 ; i++) { - [wastedMemory appendData:[NSMutableData dataWithLength:size]]; - } - // Or if all that fails, just force a memory warning manually :) - [[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]; +// int size = 500000; +// for (int i = 0; i < 10000 ; i++) { +// [wastedMemory appendData:[NSMutableData dataWithLength:size]]; +// } +// // Or if all that fails, just force a memory warning manually :) +// [[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]; + + +// NSArray *paths = @[@"/home/test.txt"]; +// +// NSString *domain = @"com.backtrace.exampleApp"; +// NSInteger errorCode = 1001; +// NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Something went wrong." }; +// +// NSError *error = [NSError errorWithDomain:domain +// code:errorCode +// userInfo:userInfo]; +// +// NSLog(@"Error: %@", error); +// +// [[BacktraceClient shared] sendWithError:error attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; + + + +// NSException *exception = [self generateException]; +// @throw exception; + +// NSArray *paths = @[@"/home/test.txt"]; +// +// @try { +// [self throwTestException]; +// +// } @catch (NSException *exception) { +// NSLog(@"Exception: %@", exception.callStackSymbols); +// +// +// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; +// +// } + + NSLog(@"Starting Crash Trigger Simulation..."); + [self simulateCrash]; + [[NSRunLoop currentRunLoop] run]; + } - (IBAction) liveReportAction: (id) sender { NSArray *paths = @[@"/home/test.txt"]; - [[BacktraceClient shared] sendWithAttachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { - NSLog(@"%@", result.message); - }]; +// [[BacktraceClient shared] sendWithAttachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; + +// NSString *domain = @"com.backtrace.exampleApp"; +// NSInteger errorCode = 1001; +// NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Something went wrong." }; +// +// NSError *error = [NSError errorWithDomain:domain +// code:errorCode +// userInfo:userInfo]; +// +// NSLog(@"Error: %@", error); +// +// [[BacktraceClient shared] sendWithError:error attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; + + // @try { + // [self throwTestException]; + // + // } @catch (NSException *exception) { + // NSLog(@"Exception: %@", exception.callStackSymbols); + // [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { + // NSLog(@"%@", result.message); + // }]; + // + // + // } + + // [[BacktraceClient shared] sendWithMessage:@"This is a message for Konrad" attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { + // NSLog(@"%@", result.message); + // }]; + + +// @try { +// [self throwTestException]; +// +// } @catch (NSException *exception) { +// NSLog(@"Exception: %@", exception.callStackSymbols); +// +// +//// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +//// NSLog(@"%@", result.message); +//// }]; +// +// [[BacktraceClient shared] sendWithMessage:@"OVERFLOW" attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; +// +// } + + + [[NSRunLoop currentRunLoop] run]; + + + +// NSException *exception = [self generateException]; +// +// @try { +// +// @throw exception; +// +// } @catch (NSException *exception) { +// NSLog(@"Exception: %@", exception.callStackSymbols); +// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; +// +// +// } + + + + +// @try { +// NSString *domain = @"com.backtrace.exampleApp"; +// NSInteger errorCode = 1001; +// NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Something went wrong." }; +// +// NSError *error = [NSError errorWithDomain:domain +// code:errorCode +// userInfo:userInfo]; +// +// NSLog(@"Error Try: %@", error); +// +// } @catch (NSError *error) { +// NSLog(@"Error @catch: %@", error); +// [[BacktraceClient shared] sendWithError:error attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; +// +// } + + +// @try { +// [self throwTestException]; +// +// } @catch (NSException *exception) { +// NSLog(@"Exception: %@", exception.callStackSymbols); +// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; +// +// +// } + +// [[BacktraceClient shared] sendWithMessage:@"This is a message for Konrad" attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { +// NSLog(@"%@", result.message); +// }]; + +} + +- (NSException *)generateException { + NSException *exception = [NSException exceptionWithName:@"Generated Exception" + reason:@"This is a generated exception." + userInfo:@{@"ExampleKey": @"ExampleValue"}]; + return exception; +} + +- (void)throwTestException { + @throw [NSException exceptionWithName:@"TestException" + reason:@"TestException reason." + userInfo:@{@"key": @"value"}]; } - (IBAction) crashAction: (id) sender { @@ -38,5 +201,96 @@ - (IBAction) crashAction: (id) sender { array[1]; } +- (void)simulateCrash { + // Generate large files to simulate excessive attachments + //NSArray *attachmentPaths = [self generateInsaneAttachments]; + + + NSArray *attachmentPaths = nil; + + @try { + attachmentPaths = [self generateInsaneAttachments]; + } @catch (NSException *exception) { + NSLog(@"An exception occurred: %@", exception.reason); + //attachmentPaths = @[]; // Return an empty array if an exception occurs + + // Send the report with the large attachments + [[BacktraceClient shared] sendWithMessage:exception.name + attachmentPaths:attachmentPaths + completion:^(BacktraceResult * _Nonnull result) { + NSLog(@"%@", result.message); + }]; + + } + + + + +} + +- (NSArray *)generateLargeAttachments { + NSMutableArray *paths = [NSMutableArray array]; + NSString *directory = NSTemporaryDirectory(); + + for (int i = 0; i < 5; i++) { // Generate 5 large files + NSString *filePath = [directory stringByAppendingPathComponent:[NSString stringWithFormat:@"large_file_%d.dat", i]]; + NSData *largeData = [self generateLargeData:1024 * 1024 * 50]; // 50 MB per file + + NSError *error = nil; + [largeData writeToFile:filePath options:NSDataWritingAtomic error:&error]; + if (error) { + NSLog(@"Failed to write file: %@", error.localizedDescription); + } else { + [paths addObject:filePath]; + NSLog(@"Generated file: %@", filePath); + } + } + return paths; +} + +- (NSArray *)generateInsaneAttachments { + NSMutableArray *paths = [NSMutableArray array]; + NSString *directory = NSTemporaryDirectory(); + + // Create 20 large files, each ~200 MB + for (int i = 0; i < 20; i++) { + NSString *filePath = [directory stringByAppendingPathComponent:[NSString stringWithFormat:@"insane_file_%d.dat", i]]; + NSData *largeData = [self generateLargeData:1024 * 1024 * 200]; // 200 MB per file + + NSError *error = nil; + [largeData writeToFile:filePath options:NSDataWritingAtomic error:&error]; + if (error) { + NSLog(@"Failed to write file: %@", error.localizedDescription); + } else { + [paths addObject:filePath]; + NSLog(@"Generated file: %@", filePath); + } + } + return paths; +} + +- (NSData *)generateLargeData:(NSUInteger)size { + // Fill memory with huge blocks of data + NSMutableData *data = [NSMutableData dataWithCapacity:size]; + uint8_t buffer[1024]; + memset(buffer, 'B', sizeof(buffer)); + + for (NSUInteger i = 0; i < size / sizeof(buffer); i++) { + [data appendBytes:buffer length:sizeof(buffer)]; + } + return data; +} + +//- (NSData *)generateLargeData:(NSUInteger)size { +// // Create a block of repeating bytes to simulate large data +// NSMutableData *data = [NSMutableData dataWithCapacity:size]; +// uint8_t buffer[1024]; // 1 KB buffer +// memset(buffer, 'A', sizeof(buffer)); +// +// for (NSUInteger i = 0; i < size / sizeof(buffer); i++) { +// [data appendBytes:buffer length:sizeof(buffer)]; +// } +// return data; +//} @end From 63f19f05e520c8ca93db2bf2a8503a4bdc096a4f Mon Sep 17 00:00:00 2001 From: melekr Date: Mon, 6 Jan 2025 18:30:27 -0500 Subject: [PATCH 04/14] Update MACOSX_DEPLOYMENT_TARGET to v.11 Big Sur --- Backtrace.xcodeproj/project.pbxproj | 48 ++++++++++++++--------------- Package.swift | 2 +- Podfile.lock | 4 +-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Backtrace.xcodeproj/project.pbxproj b/Backtrace.xcodeproj/project.pbxproj index 5e82353f..043b7211 100644 --- a/Backtrace.xcodeproj/project.pbxproj +++ b/Backtrace.xcodeproj/project.pbxproj @@ -2692,7 +2692,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2771,7 +2771,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace; @@ -2848,7 +2848,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2916,7 +2916,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-tvOSTests"; @@ -3003,7 +3003,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3086,7 +3086,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace; @@ -3166,7 +3166,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3239,7 +3239,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-macOSTests"; @@ -3315,7 +3315,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3389,7 +3389,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.tvos.example; @@ -3413,7 +3413,7 @@ buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MARKETING_VERSION = 2.0.7; STRIP_STYLE = debugging; STRIP_SWIFT_SYMBOLS = NO; @@ -3427,7 +3427,7 @@ buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MARKETING_VERSION = 2.0.7; STRIP_STYLE = debugging; STRIP_SWIFT_SYMBOLS = NO; @@ -3507,7 +3507,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3594,7 +3594,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace; @@ -3679,7 +3679,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3755,7 +3755,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-iOSTests"; @@ -3837,7 +3837,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3915,7 +3915,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.ios.example; @@ -3975,7 +3975,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JWKXD469L2; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3999,7 +3999,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -4053,7 +4053,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JWKXD469L2; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4071,7 +4071,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.objc.ios.examples; @@ -4150,7 +4150,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -4221,7 +4221,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 11.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.macos.example; diff --git a/Package.swift b/Package.swift index 725c2eb5..fc3b890b 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ let package = Package( name: "Backtrace", platforms: [ .iOS(.v12), - .macOS(.v10_13), + .macOS(.v11), .tvOS(.v12) ], products: [ diff --git a/Podfile.lock b/Podfile.lock index 1b403e1c..13a6b872 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Backtrace (2.0.6): + - Backtrace (2.0.7): - PLCrashReporter (= 1.11.1) - Nimble (10.0.0) - PLCrashReporter (1.11.1) @@ -22,7 +22,7 @@ EXTERNAL SOURCES: :path: "./Backtrace.podspec" SPEC CHECKSUMS: - Backtrace: 72bea7c55570b42ab18dd7218ce41ceef3edca14 + Backtrace: a686b3bfb9a7a176080e259f7f8dd7a5f521d957 Nimble: 5316ef81a170ce87baf72dd961f22f89a602ff84 PLCrashReporter: 5d2d3967afe0efad61b3048d617e2199a5d1b787 Quick: 749aa754fd1e7d984f2000fe051e18a3a9809179 From 7983155ea8f3fd571e0c3eda1760791b4865cfa8 Mon Sep 17 00:00:00 2001 From: melekr Date: Mon, 6 Jan 2025 18:31:14 -0500 Subject: [PATCH 05/14] Update mimeTypeForPath method for tvOS 14.0 & macOS 11.0 --- Sources/Features/Repository/Model/Attachment.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Features/Repository/Model/Attachment.swift b/Sources/Features/Repository/Model/Attachment.swift index 12fb2b1f..6690fd3c 100644 --- a/Sources/Features/Repository/Model/Attachment.swift +++ b/Sources/Features/Repository/Model/Attachment.swift @@ -48,7 +48,7 @@ struct Attachment { /// - Parameter fileUrl: File URL /// - Returns: MIME type as a string, or "application/octet-stream" as fallback static private func mimeTypeForPath(fileUrl: URL) -> String { - if #available(iOS 14.0, *) { + if #available(iOS 14.0, tvOS 14.0, macOS 11.0, *) { if let type = UTType(filenameExtension: fileUrl.pathExtension)?.preferredMIMEType { return type } From b447b736d0b5eba572fb716c445e348629807f66 Mon Sep 17 00:00:00 2001 From: melekr Date: Mon, 6 Jan 2025 18:33:06 -0500 Subject: [PATCH 06/14] Generate live report when reporting without explicit exception --- Sources/Public/BacktraceCrashReporter.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/Public/BacktraceCrashReporter.swift b/Sources/Public/BacktraceCrashReporter.swift index aa481f35..e5783f5a 100644 --- a/Sources/Public/BacktraceCrashReporter.swift +++ b/Sources/Public/BacktraceCrashReporter.swift @@ -50,8 +50,13 @@ extension BacktraceCrashReporter: CrashReporting { func generateLiveReport(exception: NSException? = nil, attributes: Attributes, attachmentPaths: [String] = []) throws -> BacktraceReport { + let reportData: Data + if let exception = exception { + reportData = try reporter.generateLiveReport(with: exception) + } else { + reportData = try reporter.generateLiveReport() + } - let reportData = try reporter.generateLiveReport(with: exception) return try BacktraceReport(report: reportData, attributes: attributes, attachmentPaths: attachmentPaths) } From 84619ebb23dffe90fbaa0731d2021820109df868 Mon Sep 17 00:00:00 2001 From: melekr Date: Mon, 6 Jan 2025 18:39:20 -0500 Subject: [PATCH 07/14] Update ViewController.m --- Examples/Example-iOS-ObjC/ViewController.m | 273 +-------------------- 1 file changed, 9 insertions(+), 264 deletions(-) diff --git a/Examples/Example-iOS-ObjC/ViewController.m b/Examples/Example-iOS-ObjC/ViewController.m index b2f83ac7..7f2450ba 100644 --- a/Examples/Example-iOS-ObjC/ViewController.m +++ b/Examples/Example-iOS-ObjC/ViewController.m @@ -18,182 +18,19 @@ - (void)viewDidLoad { - (IBAction) outOfMemoryReportAction: (id) sender { // The trick is: to aggressively take up memory but not allocate a block too large to cause a crash // This is obviously device dependent, so the 500k may have to be tweaked -// int size = 500000; -// for (int i = 0; i < 10000 ; i++) { -// [wastedMemory appendData:[NSMutableData dataWithLength:size]]; -// } -// // Or if all that fails, just force a memory warning manually :) -// [[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]; - - -// NSArray *paths = @[@"/home/test.txt"]; -// -// NSString *domain = @"com.backtrace.exampleApp"; -// NSInteger errorCode = 1001; -// NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Something went wrong." }; -// -// NSError *error = [NSError errorWithDomain:domain -// code:errorCode -// userInfo:userInfo]; -// -// NSLog(@"Error: %@", error); -// -// [[BacktraceClient shared] sendWithError:error attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; - - - -// NSException *exception = [self generateException]; -// @throw exception; - -// NSArray *paths = @[@"/home/test.txt"]; -// -// @try { -// [self throwTestException]; -// -// } @catch (NSException *exception) { -// NSLog(@"Exception: %@", exception.callStackSymbols); -// -// -// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; -// -// } - - NSLog(@"Starting Crash Trigger Simulation..."); - [self simulateCrash]; - [[NSRunLoop currentRunLoop] run]; - + int size = 500000; + for (int i = 0; i < 10000 ; i++) { + [wastedMemory appendData:[NSMutableData dataWithLength:size]]; + } + // Or if all that fails, just force a memory warning manually :) + [[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]; } - (IBAction) liveReportAction: (id) sender { NSArray *paths = @[@"/home/test.txt"]; -// [[BacktraceClient shared] sendWithAttachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; - -// NSString *domain = @"com.backtrace.exampleApp"; -// NSInteger errorCode = 1001; -// NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Something went wrong." }; -// -// NSError *error = [NSError errorWithDomain:domain -// code:errorCode -// userInfo:userInfo]; -// -// NSLog(@"Error: %@", error); -// -// [[BacktraceClient shared] sendWithError:error attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; - - // @try { - // [self throwTestException]; - // - // } @catch (NSException *exception) { - // NSLog(@"Exception: %@", exception.callStackSymbols); - // [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { - // NSLog(@"%@", result.message); - // }]; - // - // - // } - - // [[BacktraceClient shared] sendWithMessage:@"This is a message for Konrad" attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { - // NSLog(@"%@", result.message); - // }]; - - -// @try { -// [self throwTestException]; -// -// } @catch (NSException *exception) { -// NSLog(@"Exception: %@", exception.callStackSymbols); -// -// -//// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -//// NSLog(@"%@", result.message); -//// }]; -// -// [[BacktraceClient shared] sendWithMessage:@"OVERFLOW" attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; -// -// } - - - [[NSRunLoop currentRunLoop] run]; - - - -// NSException *exception = [self generateException]; -// -// @try { -// -// @throw exception; -// -// } @catch (NSException *exception) { -// NSLog(@"Exception: %@", exception.callStackSymbols); -// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; -// -// -// } - - - - -// @try { -// NSString *domain = @"com.backtrace.exampleApp"; -// NSInteger errorCode = 1001; -// NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Something went wrong." }; -// -// NSError *error = [NSError errorWithDomain:domain -// code:errorCode -// userInfo:userInfo]; -// -// NSLog(@"Error Try: %@", error); -// -// } @catch (NSError *error) { -// NSLog(@"Error @catch: %@", error); -// [[BacktraceClient shared] sendWithError:error attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; -// -// } - - -// @try { -// [self throwTestException]; -// -// } @catch (NSException *exception) { -// NSLog(@"Exception: %@", exception.callStackSymbols); -// [[BacktraceClient shared] sendWithException:exception attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; -// -// -// } - -// [[BacktraceClient shared] sendWithMessage:@"This is a message for Konrad" attachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { -// NSLog(@"%@", result.message); -// }]; - -} - -- (NSException *)generateException { - NSException *exception = [NSException exceptionWithName:@"Generated Exception" - reason:@"This is a generated exception." - userInfo:@{@"ExampleKey": @"ExampleValue"}]; - return exception; -} - -- (void)throwTestException { - @throw [NSException exceptionWithName:@"TestException" - reason:@"TestException reason." - userInfo:@{@"key": @"value"}]; + [[BacktraceClient shared] sendWithAttachmentPaths:paths completion:^(BacktraceResult * _Nonnull result) { + NSLog(@"%@", result.message); + }]; } - (IBAction) crashAction: (id) sender { @@ -201,96 +38,4 @@ - (IBAction) crashAction: (id) sender { array[1]; } -- (void)simulateCrash { - // Generate large files to simulate excessive attachments - //NSArray *attachmentPaths = [self generateInsaneAttachments]; - - - NSArray *attachmentPaths = nil; - - @try { - attachmentPaths = [self generateInsaneAttachments]; - } @catch (NSException *exception) { - NSLog(@"An exception occurred: %@", exception.reason); - //attachmentPaths = @[]; // Return an empty array if an exception occurs - - // Send the report with the large attachments - [[BacktraceClient shared] sendWithMessage:exception.name - attachmentPaths:attachmentPaths - completion:^(BacktraceResult * _Nonnull result) { - NSLog(@"%@", result.message); - }]; - - } - - - - -} - -- (NSArray *)generateLargeAttachments { - NSMutableArray *paths = [NSMutableArray array]; - NSString *directory = NSTemporaryDirectory(); - - for (int i = 0; i < 5; i++) { // Generate 5 large files - NSString *filePath = [directory stringByAppendingPathComponent:[NSString stringWithFormat:@"large_file_%d.dat", i]]; - NSData *largeData = [self generateLargeData:1024 * 1024 * 50]; // 50 MB per file - - NSError *error = nil; - [largeData writeToFile:filePath options:NSDataWritingAtomic error:&error]; - if (error) { - NSLog(@"Failed to write file: %@", error.localizedDescription); - } else { - [paths addObject:filePath]; - NSLog(@"Generated file: %@", filePath); - } - } - return paths; -} - -- (NSArray *)generateInsaneAttachments { - NSMutableArray *paths = [NSMutableArray array]; - NSString *directory = NSTemporaryDirectory(); - - // Create 20 large files, each ~200 MB - for (int i = 0; i < 20; i++) { - NSString *filePath = [directory stringByAppendingPathComponent:[NSString stringWithFormat:@"insane_file_%d.dat", i]]; - NSData *largeData = [self generateLargeData:1024 * 1024 * 200]; // 200 MB per file - - NSError *error = nil; - [largeData writeToFile:filePath options:NSDataWritingAtomic error:&error]; - if (error) { - NSLog(@"Failed to write file: %@", error.localizedDescription); - } else { - [paths addObject:filePath]; - NSLog(@"Generated file: %@", filePath); - } - } - return paths; -} - -- (NSData *)generateLargeData:(NSUInteger)size { - // Fill memory with huge blocks of data - NSMutableData *data = [NSMutableData dataWithCapacity:size]; - uint8_t buffer[1024]; - memset(buffer, 'B', sizeof(buffer)); - - for (NSUInteger i = 0; i < size / sizeof(buffer); i++) { - [data appendBytes:buffer length:sizeof(buffer)]; - } - return data; -} - -//- (NSData *)generateLargeData:(NSUInteger)size { -// // Create a block of repeating bytes to simulate large data -// NSMutableData *data = [NSMutableData dataWithCapacity:size]; -// uint8_t buffer[1024]; // 1 KB buffer -// memset(buffer, 'A', sizeof(buffer)); -// -// for (NSUInteger i = 0; i < size / sizeof(buffer); i++) { -// [data appendBytes:buffer length:sizeof(buffer)]; -// } -// return data; -//} - @end From c2fb53aaed243328bfb49282d9bfa8e7a845a4f9 Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 14 Jan 2025 16:41:24 -0500 Subject: [PATCH 08/14] Update MultipartRequest with file creation checks and error handling --- .../Features/Network/MultipartRequest.swift | 95 ++++++++++++------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/Sources/Features/Network/MultipartRequest.swift b/Sources/Features/Network/MultipartRequest.swift index bbca2fc7..8cf86d74 100644 --- a/Sources/Features/Network/MultipartRequest.swift +++ b/Sources/Features/Network/MultipartRequest.swift @@ -51,44 +51,65 @@ extension MultipartRequest { // temporary file to stream data let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) - FileManager.default.createFile(atPath: tempURL.path, contents: nil, attributes: nil) - let fileHandle = try FileHandle(forWritingTo: tempURL) - defer { fileHandle.closeFile() } - - // attributes - for attribute in report.attributes { - writeToFile(fileHandle, "--\(boundary)\r\n") - writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n") - writeToFile(fileHandle, "\(attribute.value)\r\n") - } - - // report - writeToFile(fileHandle, "--\(boundary)\r\n") - writeToFile(fileHandle, "Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n") - writeToFile(fileHandle, "Content-Type: application/octet-stream\r\n\r\n") - fileHandle.write(report.reportData) - writeToFile(fileHandle, "\r\n") - - // attachments - for attachment in Set(report.attachmentPaths).compactMap(Attachment.init(filePath:)) { - writeToFile(fileHandle, "--\(boundary)\r\n") - writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") - writeToFile(fileHandle, "Content-Type: \(attachment.mimeType)\r\n\r\n") - fileHandle.write(attachment.data) - writeToFile(fileHandle, "\r\n") + do { + let fileCreated = FileManager.default.createFile(atPath: tempURL.path, contents: nil, attributes: nil) + if !fileCreated { + throw HttpError.fileCreationFailed(tempURL) + } + + let fileHandle = try FileHandle(forWritingTo: tempURL) + defer { + if #available(iOS 13.0, *) { + try? fileHandle.close() + } else { + fileHandle.closeFile() + } + } + + // attributes + for attribute in report.attributes { + try writeToFile(fileHandle, "--\(boundary)\r\n") + try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n") + try writeToFile(fileHandle, "\(attribute.value)\r\n") + } + + // report + try writeToFile(fileHandle, "--\(boundary)\r\n") + try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n") + try writeToFile(fileHandle, "Content-Type: application/octet-stream\r\n\r\n") + fileHandle.write(report.reportData) + try writeToFile(fileHandle, "\r\n") + + // attachments + for attachmentPath in Set(report.attachmentPaths) { + guard let attachment = Attachment(filePath: attachmentPath) else { + BacktraceLogger.error("Failed to create attachment for path: \(attachmentPath)") + continue + } + + try writeToFile(fileHandle, "--\(boundary)\r\n") + try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") + try writeToFile(fileHandle, "Content-Type: \(attachment.mimeType)\r\n\r\n") + fileHandle.write(attachment.data) + try writeToFile(fileHandle, "\r\n") + } + + // Final boundary + try writeToFile(fileHandle, "--\(boundary)--\r\n") + + // Set Content-Length + let fileSize = try FileManager.default.attributesOfItem(atPath: tempURL.path)[.size] as? Int ?? 0 + multipartRequest.setValue("\(fileSize)", forHTTPHeaderField: "Content-Length") + + // Attach file stream to HTTP body + multipartRequest.httpBodyStream = InputStream(url: tempURL) + + } catch { + BacktraceLogger.error("Error during multipart form creation: \(error.localizedDescription)") + throw HttpError.multipartFormError(error) } - // Final boundary - writeToFile(fileHandle, "--\(boundary)--\r\n") - - // Set Content-Length - let fileSize = try FileManager.default.attributesOfItem(atPath: tempURL.path)[.size] as? Int ?? 0 - multipartRequest.setValue("\(fileSize)", forHTTPHeaderField: "Content-Length") - - // Attach file stream to HTTP body - multipartRequest.httpBodyStream = InputStream(url: tempURL) - return multipartRequest } @@ -96,9 +117,11 @@ extension MultipartRequest { return "Boundary-\(NSUUID().uuidString)" } - private static func writeToFile(_ fileHandle: FileHandle, _ string: String) { + private static func writeToFile(_ fileHandle: FileHandle, _ string: String) throws { if let data = string.data(using: .utf8) { fileHandle.write(data) + } else { + throw HttpError.fileWriteFailed } } } From ef88290bc6b55853e4e8e9c5ff0415cfadeb6ba4 Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 14 Jan 2025 16:42:05 -0500 Subject: [PATCH 09/14] MultipartRequest error granularity --- Sources/Features/Error/BacktraceError.swift | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Sources/Features/Error/BacktraceError.swift b/Sources/Features/Error/BacktraceError.swift index 74368361..6fdf63bd 100644 --- a/Sources/Features/Error/BacktraceError.swift +++ b/Sources/Features/Error/BacktraceError.swift @@ -16,6 +16,10 @@ enum NetworkError: BacktraceError { enum HttpError: BacktraceError { case malformedUrl(URL) + case fileCreationFailed(URL) + case fileWriteFailed + case attachmentError(String) + case multipartFormError(Error) case unknownError } @@ -42,7 +46,7 @@ enum CodingError: BacktraceError { extension HttpError { var backtraceStatus: BacktraceReportStatus { switch self { - case .malformedUrl: + case .malformedUrl, .fileCreationFailed, .fileWriteFailed, .attachmentError, .multipartFormError: return .unknownError case .unknownError: return .serverError @@ -62,8 +66,18 @@ extension NetworkError { extension HttpError { var localizedDescription: String { switch self { - case .malformedUrl(let url): return "Provided URL cannot be parsed: \(url)." - case .unknownError: return "Unknown error occurred." + case .malformedUrl(let url): + return "Provided URL cannot be parsed: \(url)." + case .fileCreationFailed(let url): + return "File Error occurred: \(url)." + case .fileWriteFailed: + return "File Write Error occurred." + case .attachmentError(let string): + return "Attachment Error occurred: \(string)." + case .multipartFormError(let error): + return "Multipart Form Error occurred: \(error.localizedDescription)." + case .unknownError: + return "Unknown error occurred." } } } From e7e659e21f413b2f004741800b2de2851650f934 Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 14 Jan 2025 16:42:48 -0500 Subject: [PATCH 10/14] Remove extra try when generating Live Report --- Sources/Public/BacktraceCrashReporter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Public/BacktraceCrashReporter.swift b/Sources/Public/BacktraceCrashReporter.swift index e5783f5a..b899662a 100644 --- a/Sources/Public/BacktraceCrashReporter.swift +++ b/Sources/Public/BacktraceCrashReporter.swift @@ -54,7 +54,7 @@ extension BacktraceCrashReporter: CrashReporting { if let exception = exception { reportData = try reporter.generateLiveReport(with: exception) } else { - reportData = try reporter.generateLiveReport() + reportData = reporter.generateLiveReport() } return try BacktraceReport(report: reportData, attributes: attributes, attachmentPaths: attachmentPaths) From e5cbedba1bd9d7b91e69b25b114d77b9d0734c28 Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 14 Jan 2025 17:14:45 -0500 Subject: [PATCH 11/14] Revert MACOSX_DEPLOYMENT_TARGET to v10.13 --- Backtrace.xcodeproj/project.pbxproj | 44 +++++++++---------- Package.swift | 2 +- .../Features/Network/MultipartRequest.swift | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Backtrace.xcodeproj/project.pbxproj b/Backtrace.xcodeproj/project.pbxproj index 043b7211..377500c2 100644 --- a/Backtrace.xcodeproj/project.pbxproj +++ b/Backtrace.xcodeproj/project.pbxproj @@ -2692,7 +2692,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2771,7 +2771,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace; @@ -2848,7 +2848,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2916,7 +2916,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-tvOSTests"; @@ -3003,7 +3003,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3086,7 +3086,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace; @@ -3166,7 +3166,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3239,7 +3239,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-macOSTests"; @@ -3315,7 +3315,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3389,7 +3389,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.tvos.example; @@ -3413,7 +3413,7 @@ buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 2.0.7; STRIP_STYLE = debugging; STRIP_SWIFT_SYMBOLS = NO; @@ -3427,7 +3427,7 @@ buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 2.0.7; STRIP_STYLE = debugging; STRIP_SWIFT_SYMBOLS = NO; @@ -3507,7 +3507,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3594,7 +3594,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.Backtrace; @@ -3679,7 +3679,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3755,7 +3755,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "Backtrace.io.Backtrace-iOSTests"; @@ -3837,7 +3837,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -3915,7 +3915,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.ios.example; @@ -3999,7 +3999,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -4071,7 +4071,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.objc.ios.examples; @@ -4150,7 +4150,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -4221,7 +4221,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.5; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = Backtrace.io.swift.macos.example; diff --git a/Package.swift b/Package.swift index fc3b890b..725c2eb5 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,7 @@ let package = Package( name: "Backtrace", platforms: [ .iOS(.v12), - .macOS(.v11), + .macOS(.v10_13), .tvOS(.v12) ], products: [ diff --git a/Sources/Features/Network/MultipartRequest.swift b/Sources/Features/Network/MultipartRequest.swift index 8cf86d74..26bb7c66 100644 --- a/Sources/Features/Network/MultipartRequest.swift +++ b/Sources/Features/Network/MultipartRequest.swift @@ -60,7 +60,7 @@ extension MultipartRequest { let fileHandle = try FileHandle(forWritingTo: tempURL) defer { - if #available(iOS 13.0, *) { + if #available(iOS 13.0, tvOS 13.0, macOS 11.0, *) { try? fileHandle.close() } else { fileHandle.closeFile() From bb3f7f6ef94a22137c9188eaffcc17669c578a46 Mon Sep 17 00:00:00 2001 From: melekr Date: Tue, 14 Jan 2025 17:43:27 -0500 Subject: [PATCH 12/14] Reduce writeToFile operations --- Sources/Features/Network/MultipartRequest.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/Features/Network/MultipartRequest.swift b/Sources/Features/Network/MultipartRequest.swift index 26bb7c66..52fcc0fa 100644 --- a/Sources/Features/Network/MultipartRequest.swift +++ b/Sources/Features/Network/MultipartRequest.swift @@ -68,16 +68,19 @@ extension MultipartRequest { } // attributes + var attributesString = "" for attribute in report.attributes { - try writeToFile(fileHandle, "--\(boundary)\r\n") - try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n") - try writeToFile(fileHandle, "\(attribute.value)\r\n") + attributesString += "--\(boundary)\r\n" + attributesString += "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n" + attributesString += "\(attribute.value)\r\n" } + try writeToFile(fileHandle, attributesString) // report - try writeToFile(fileHandle, "--\(boundary)\r\n") - try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n") - try writeToFile(fileHandle, "Content-Type: application/octet-stream\r\n\r\n") + var reportString = "--\(boundary)\r\n" + reportString += "Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n" + reportString += "Content-Type: application/octet-stream\r\n\r\n" + try writeToFile(fileHandle, reportString) fileHandle.write(report.reportData) try writeToFile(fileHandle, "\r\n") From e1cd5178e9e092f98a66561f5f3fca67afe91969 Mon Sep 17 00:00:00 2001 From: melekr Date: Wed, 15 Jan 2025 11:51:18 -0500 Subject: [PATCH 13/14] Cleanup --- Backtrace.xcodeproj/project.pbxproj | 8 ++++---- Sources/Public/BacktraceCrashReporter.swift | 7 +------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Backtrace.xcodeproj/project.pbxproj b/Backtrace.xcodeproj/project.pbxproj index 377500c2..09454392 100644 --- a/Backtrace.xcodeproj/project.pbxproj +++ b/Backtrace.xcodeproj/project.pbxproj @@ -3814,7 +3814,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = JWKXD469L2; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3898,7 +3898,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = JWKXD469L2; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3975,7 +3975,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = JWKXD469L2; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -4053,7 +4053,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = JWKXD469L2; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/Sources/Public/BacktraceCrashReporter.swift b/Sources/Public/BacktraceCrashReporter.swift index b899662a..aa481f35 100644 --- a/Sources/Public/BacktraceCrashReporter.swift +++ b/Sources/Public/BacktraceCrashReporter.swift @@ -50,13 +50,8 @@ extension BacktraceCrashReporter: CrashReporting { func generateLiveReport(exception: NSException? = nil, attributes: Attributes, attachmentPaths: [String] = []) throws -> BacktraceReport { - let reportData: Data - if let exception = exception { - reportData = try reporter.generateLiveReport(with: exception) - } else { - reportData = reporter.generateLiveReport() - } + let reportData = try reporter.generateLiveReport(with: exception) return try BacktraceReport(report: reportData, attributes: attributes, attachmentPaths: attachmentPaths) } From 4c2fbdee40ef3b0e4914afa6cb1c9d21e04529df Mon Sep 17 00:00:00 2001 From: melekr Date: Fri, 17 Jan 2025 16:38:58 -0500 Subject: [PATCH 14/14] Replace file writes with OutputStream --- Sources/Features/Error/BacktraceError.swift | 7 +- .../Features/Network/MultipartRequest.swift | 115 +++++++++++------- 2 files changed, 76 insertions(+), 46 deletions(-) diff --git a/Sources/Features/Error/BacktraceError.swift b/Sources/Features/Error/BacktraceError.swift index 6fdf63bd..f147451d 100644 --- a/Sources/Features/Error/BacktraceError.swift +++ b/Sources/Features/Error/BacktraceError.swift @@ -14,12 +14,15 @@ enum NetworkError: BacktraceError { case connectionError(Error) } +//TODO: Create and update stream error category enum HttpError: BacktraceError { case malformedUrl(URL) case fileCreationFailed(URL) case fileWriteFailed case attachmentError(String) case multipartFormError(Error) + case streamReadFailed + case streamWriteFailed case unknownError } @@ -46,7 +49,7 @@ enum CodingError: BacktraceError { extension HttpError { var backtraceStatus: BacktraceReportStatus { switch self { - case .malformedUrl, .fileCreationFailed, .fileWriteFailed, .attachmentError, .multipartFormError: + case .malformedUrl, .fileCreationFailed, .fileWriteFailed, .attachmentError, .multipartFormError, .streamReadFailed, .streamWriteFailed: return .unknownError case .unknownError: return .serverError @@ -70,7 +73,7 @@ extension HttpError { return "Provided URL cannot be parsed: \(url)." case .fileCreationFailed(let url): return "File Error occurred: \(url)." - case .fileWriteFailed: + case .fileWriteFailed, .streamReadFailed, .streamWriteFailed: return "File Write Error occurred." case .attachmentError(let string): return "Attachment Error occurred: \(string)." diff --git a/Sources/Features/Network/MultipartRequest.swift b/Sources/Features/Network/MultipartRequest.swift index 52fcc0fa..da1f75bf 100644 --- a/Sources/Features/Network/MultipartRequest.swift +++ b/Sources/Features/Network/MultipartRequest.swift @@ -48,25 +48,16 @@ extension MultipartRequest { var multipartRequest = urlRequest let boundary = generateBoundaryString() multipartRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - - // temporary file to stream data - let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) - + + // output stream + // TODO: bind input & output streams + let outputStream = OutputStream.toMemory() + outputStream.open() + defer { outputStream.close() } + + let writeLock = DispatchQueue(label: "backtrace.multipartRequest.writeLock") + do { - let fileCreated = FileManager.default.createFile(atPath: tempURL.path, contents: nil, attributes: nil) - if !fileCreated { - throw HttpError.fileCreationFailed(tempURL) - } - - let fileHandle = try FileHandle(forWritingTo: tempURL) - defer { - if #available(iOS 13.0, tvOS 13.0, macOS 11.0, *) { - try? fileHandle.close() - } else { - fileHandle.closeFile() - } - } - // attributes var attributesString = "" for attribute in report.attributes { @@ -74,65 +65,101 @@ extension MultipartRequest { attributesString += "Content-Disposition: form-data; name=\"\(attribute.key)\"\r\n\r\n" attributesString += "\(attribute.value)\r\n" } - try writeToFile(fileHandle, attributesString) - + try writeToStream(outputStream, attributesString, writeLock: writeLock) + // report var reportString = "--\(boundary)\r\n" reportString += "Content-Disposition: form-data; name=\"upload_file\"; filename=\"upload_file\"\r\n" reportString += "Content-Type: application/octet-stream\r\n\r\n" - try writeToFile(fileHandle, reportString) - fileHandle.write(report.reportData) - try writeToFile(fileHandle, "\r\n") - + try writeToStream(outputStream, reportString, writeLock: writeLock) + try writeLock.sync { + let data = report.reportData + let bytesWritten = data.withUnsafeBytes { + guard let baseAddress = $0.bindMemory(to: UInt8.self).baseAddress else { + return -1 + } + return outputStream.write(baseAddress, maxLength: data.count) + } + if bytesWritten < 0 { + throw HttpError.streamWriteFailed + } + } + try writeToStream(outputStream, "\r\n", writeLock: writeLock) + // attachments for attachmentPath in Set(report.attachmentPaths) { guard let attachment = Attachment(filePath: attachmentPath) else { BacktraceLogger.error("Failed to create attachment for path: \(attachmentPath)") continue } - - try writeToFile(fileHandle, "--\(boundary)\r\n") - try writeToFile(fileHandle, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n") - try writeToFile(fileHandle, "Content-Type: \(attachment.mimeType)\r\n\r\n") - fileHandle.write(attachment.data) - try writeToFile(fileHandle, "\r\n") + + try writeToStream(outputStream, "--\(boundary)\r\n", writeLock: writeLock) + try writeToStream(outputStream, "Content-Disposition: form-data; name=\"\(attachment.filename)\"; filename=\"\(attachment.filename)\"\r\n", writeLock: writeLock) + try writeToStream(outputStream, "Content-Type: \(attachment.mimeType)\r\n\r\n", writeLock: writeLock) + try writeLock.sync { + let data = attachment.data + let bytesWritten = data.withUnsafeBytes { + guard let baseAddress = $0.bindMemory(to: UInt8.self).baseAddress else { + return -1 + } + return outputStream.write(baseAddress, maxLength: data.count) + } + if bytesWritten < 0 { + throw HttpError.streamWriteFailed + } + } + try writeToStream(outputStream, "\r\n", writeLock: writeLock) } - + // Final boundary - try writeToFile(fileHandle, "--\(boundary)--\r\n") + try writeToStream(outputStream, "--\(boundary)--\r\n", writeLock: writeLock) + // Data from Output Stream + guard let data = outputStream.property(forKey: .dataWrittenToMemoryStreamKey) as? Data else { + throw HttpError.streamReadFailed + } // Set Content-Length - let fileSize = try FileManager.default.attributesOfItem(atPath: tempURL.path)[.size] as? Int ?? 0 - multipartRequest.setValue("\(fileSize)", forHTTPHeaderField: "Content-Length") - + multipartRequest.setValue("\(data.count)", forHTTPHeaderField: "Content-Length") // Attach file stream to HTTP body - multipartRequest.httpBodyStream = InputStream(url: tempURL) - + multipartRequest.httpBodyStream = InputStream(data: data) } catch { BacktraceLogger.error("Error during multipart form creation: \(error.localizedDescription)") throw HttpError.multipartFormError(error) } - + return multipartRequest } private static func generateBoundaryString() -> String { return "Boundary-\(NSUUID().uuidString)" } - - private static func writeToFile(_ fileHandle: FileHandle, _ string: String) throws { - if let data = string.data(using: .utf8) { - fileHandle.write(data) - } else { + + private static func writeToStream(_ stream: OutputStream, _ string: String, writeLock: DispatchQueue) throws { + guard let data = string.data(using: .utf8) else { + BacktraceLogger.error("Failed to convert string to UTF-8 data: \(string)") throw HttpError.fileWriteFailed } + try writeLock.sync { + let bytesWritten = data.withUnsafeBytes { + guard let baseAddress = $0.bindMemory(to: UInt8.self).baseAddress else { + return -1 + } + return stream.write(baseAddress, maxLength: data.count) + } + if bytesWritten < 0 { + throw HttpError.streamWriteFailed + } + } } } private extension NSMutableData { func appendString(_ string: String) { - guard let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) else { return } + guard let data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) else { + BacktraceLogger.error("Failed to append string as UTF-8 data: \(string)") + return + } append(data) } }