Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,15 @@ object AESCrypto {

return if (mode == "decrypt") {
// Overwrite the input file with the decrypted file
val targetPath = if (inputFile.startsWith("file://")) inputFile.substring(7) else inputFile
val targetUri = Uri.parse(inputFile)
FileInputStream(outputFileObj).use { inputStream ->
FileOutputStream(targetPath).use { fos ->
val outputStream = if (targetUri.scheme == null || targetUri.scheme == "file") {
FileOutputStream(normalizeFilePath(inputFile))
} else {
reactContext.contentResolver.openOutputStream(targetUri)
?: throw IllegalArgumentException("Cannot open output stream for URI: $targetUri")
}
outputStream.use { fos ->
val buffer = ByteArray(BUFFER_SIZE)
var numBytesRead: Int

Expand Down Expand Up @@ -194,10 +200,32 @@ object AESCrypto {
val uri = Uri.parse(filePath)

return if (uri.scheme == null || uri.scheme == "file") {
FileInputStream(uri.path ?: filePath)
// Use the decoded path for FileInputStream
val normalizedPath = normalizeFilePath(filePath)
FileInputStream(normalizedPath)
} else {
reactContext.contentResolver.openInputStream(uri)
?: throw IllegalArgumentException("Cannot open input stream for URI: $uri")
}
}

/**
* Normalize file path by removing file:// prefix and decoding URL-encoded characters
* (e.g., %20 for spaces, %D0%9D for Cyrillic chars)
*/
private fun normalizeFilePath(filePath: String): String {
var path = filePath

// Remove file:// prefix if present
if (path.startsWith("file://")) {
path = path.substring(7)
return try {
Uri.decode(path)
} catch (e: Exception) {
path
}
}

return path
}
}
91 changes: 62 additions & 29 deletions ios/algorithms/AESCrypto.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import "AESCrypto.h"
#import "CryptoUtils.h"
#import "FileUtils.h"
#import <CommonCrypto/CommonCryptor.h>

#import <MobileCrypto/MobileCrypto-Swift.h>
Expand Down Expand Up @@ -87,15 +88,36 @@ + (nullable NSString *)processFile:(NSString *)filePath operation:(CCOperation)o
NSData *keyData = [CryptoUtils decodeBase64:keyBase64];
NSData *ivData = [CryptoUtils decodeBase64:base64Iv];

NSString *normalizedFilePath = [filePath stringByReplacingOccurrencesOfString:@"file://" withString:@""];
NSString *normalizedFilePath = [FileUtils normalizeFilePath:filePath];

// Check if input file exists
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:normalizedFilePath]) {
return nil;
}

NSString *outputFileName = [@"processed_" stringByAppendingString:[normalizedFilePath lastPathComponent]];
NSString *outputFilePath = [[normalizedFilePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:outputFileName];

NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:normalizedFilePath];
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:outputFilePath append:NO];

// Validate stream creation
if (!inputStream || !outputStream) {
return nil;
}

[inputStream open];
[outputStream open];

// Validate stream opening
if ([inputStream streamStatus] != NSStreamStatusOpen || [outputStream streamStatus] != NSStreamStatusOpen) {
[inputStream close];
[outputStream close];
[fileManager removeItemAtPath:outputFilePath error:nil];
return nil;
}

size_t bufferSize = 4096;
uint8_t buffer[bufferSize];
CCCryptorRef cryptor = NULL;
Expand All @@ -104,68 +126,79 @@ + (nullable NSString *)processFile:(NSString *)filePath operation:(CCOperation)o
NSLog(@"Failed to create cryptor: %d", status);
[inputStream close];
[outputStream close];
[fileManager removeItemAtPath:outputFilePath error:nil];
return nil;
}

BOOL loopSuccess = YES;

while ([inputStream hasBytesAvailable]) {
NSInteger bytesRead = [inputStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead > 0) {

size_t dataOutMoved;
status = CCCryptorUpdate(cryptor, buffer, bytesRead, buffer, bufferSize, &dataOutMoved);
if (status == kCCSuccess) {
[outputStream write:buffer maxLength:dataOutMoved];
NSInteger bytesWritten = [outputStream write:buffer maxLength:dataOutMoved];
if (bytesWritten != (NSInteger)dataOutMoved) {
loopSuccess = NO;
break;
}
} else {
NSLog(@"Cryptor update failed: %d", status);
CCCryptorRelease(cryptor);
[inputStream close];
[outputStream close];
return nil;
loopSuccess = NO;
break;
}
} else if (bytesRead < 0) {
NSLog(@"Input stream read error");
CCCryptorRelease(cryptor);
[inputStream close];
[outputStream close];
return nil;
loopSuccess = NO;
break;
}
}

if (status == kCCSuccess) {
if (loopSuccess) {
size_t finalBytesOut;
status = CCCryptorFinal(cryptor, buffer, bufferSize, &finalBytesOut);
if (status == kCCSuccess) {
[outputStream write:buffer maxLength:finalBytesOut];
} else {
if (status == kCCSuccess && finalBytesOut > 0) {
NSInteger finalBytesWritten = [outputStream write:buffer maxLength:finalBytesOut];
if (finalBytesWritten != (NSInteger)finalBytesOut) {
NSLog(@"Output stream write error on final block");
loopSuccess = NO;
}
} else if (status != kCCSuccess) {
NSLog(@"Cryptor final failed: %d", status);
CCCryptorRelease(cryptor);
[inputStream close];
[outputStream close];
return nil;
}
}

CCCryptorRelease(cryptor);
[inputStream close];
[outputStream close];

if (status == kCCSuccess) {
if (status != kCCSuccess || !loopSuccess) {
[[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
return nil;
}

if (operation == kCCDecrypt) {
// For decrypt: atomically replace the original file with decrypted content
NSURL *originalFileURL = [NSURL fileURLWithPath:normalizedFilePath];
NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath];
NSError *error = nil;
[[NSFileManager defaultManager] replaceItemAtURL:originalFileURL
withItemAtURL:outputFileURL
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&error];
if (error) {
BOOL success = [[NSFileManager defaultManager] replaceItemAtURL:originalFileURL
withItemAtURL:outputFileURL
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&error];
if (!success) {
NSLog(@"Failed to replace original file: %@", error);
[[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
return nil;
}
return [NSString stringWithFormat:@"file://%@", normalizedFilePath];
} else {
[[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
return nil;
// For encrypt: return the processed file path
return [NSString stringWithFormat:@"file://%@", outputFilePath];
}
}

Expand Down
11 changes: 8 additions & 3 deletions ios/algorithms/FileUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,15 @@ + (BOOL)verifyFileIntegrity:(NSString *)filePath expectedChecksum:(NSString *)ex
}

+ (NSString *)normalizeFilePath:(NSString *)filePath {
if ([filePath hasPrefix:@"file://"]) {
return [filePath substringFromIndex:7];
NSString *path = filePath;

if ([path hasPrefix:@"file://"]) {
path = [path substringFromIndex:7];
NSString *decodedPath = [path stringByRemovingPercentEncoding];
return decodedPath ?: path;
}
return filePath;

return path;
}

@end
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rocket.chat/mobile-crypto",
"version": "0.2.1",
"version": "0.2.2",
"description": "Rocket.Chat Mobile Crypto",
"main": "./lib/module/index.js",
"module": "./lib/module/index.js",
Expand Down
Loading