Răsfoiți Sursa

update File detection, tuning tab2 chat and release 5.0.41

alqindiirsyam 2 luni în urmă
părinte
comite
a32413038a

+ 7 - 0
AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -36,6 +36,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     
     var noData = false
     var loadingData = true
+    var isGettingData = false
     var timerReloadData: Timer?
     
     let textViewSearch = UITextField()
@@ -768,6 +769,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     
     private func reloadAllData() {
+        print("reloadAllData")
         DispatchQueue.main.async { [weak self] in
             if self?.timerReloadData == nil {
                 self?.getData()
@@ -855,6 +857,10 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     // MARK: - Data source
     
     func getData() {
+        if self.isGettingData {
+            return
+        }
+        self.isGettingData = true
         getChats {
             self.getGroups { g1 in
                 self.groupMap.removeAll()
@@ -871,6 +877,7 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
                 DispatchQueue.main.async {
                     self.tableView.reloadData()
                     self.loadingData = false
+                    self.isGettingData = false
                 }
             }
         }

+ 17 - 1
NexilisLite/NexilisLite/Source/APIS.swift

@@ -1771,6 +1771,22 @@ public class APIS: NSObject {
         viewController.present(alert, animated: true, completion: nil)
     }
     
+    static func showWarningFile() {
+        alertControllerExpired = LibAlertController(
+            title: "⚠️ Suspicious File Detected".localized(),
+            message: "The file appears to have a mismatched name and extension, which may indicate a malicious file. Please verify the file’s source and format before uploading it.".localized(),
+            preferredStyle: .alert
+        )
+        
+        alertControllerExpired.addAction(UIAlertAction(title: "OK".localized(), style: .default, handler: nil))
+        
+        if UIApplication.shared.visibleViewController?.navigationController != nil {
+            UIApplication.shared.visibleViewController?.navigationController?.present(alertControllerExpired, animated: true, completion: nil)
+        } else {
+            UIApplication.shared.visibleViewController?.present(alertControllerExpired, animated: true, completion: nil)
+        }
+    }
+    
     private static func showEnableNotificationsAlert() {
         guard !isAlertPresented else { return }
         isAlertPresented = true
@@ -1803,7 +1819,7 @@ public class APIS: NSObject {
             preferredStyle: .alert
         )
         
-        alertControllerExpired.addAction(UIAlertAction(title: "Ok".localized(), style: .default, handler: { _ in
+        alertControllerExpired.addAction(UIAlertAction(title: "OK".localized(), style: .default, handler: { _ in
             exit(0)
         }))
         

+ 93 - 0
NexilisLite/NexilisLite/Source/Extension.swift

@@ -12,6 +12,7 @@ import SDWebImage
 import ImageIO
 import MobileCoreServices
 import CommonCrypto
+import ZIPFoundation
 
 extension Date {
     
@@ -522,6 +523,98 @@ extension URL {
         return URL(string: Utils.getURLBase() + "filepalio/image/\(named)")
     }
     
+    struct FileTypeSignature {
+        let magic: String
+        let extensions: [String]
+
+        static let knownSignatures: [FileTypeSignature] = [
+            FileTypeSignature(magic: "FFD8FF", extensions: ["jpg", "jpeg"]),
+            FileTypeSignature(magic: "89504E47", extensions: ["png"]),
+            FileTypeSignature(magic: "47494638", extensions: ["gif"]),
+            FileTypeSignature(magic: "25504446", extensions: ["pdf"]),
+            FileTypeSignature(magic: "504B0304", extensions: ["zip", "docx", "xlsx", "pptx"]),
+            FileTypeSignature(magic: "D0CF11E0", extensions: ["pptx", "docx", "xlsx", "xls", "doc", "ppt"]), // legacy/enc Office
+            FileTypeSignature(magic: "52617221", extensions: ["rar"]),
+            FileTypeSignature(magic: "00000018", extensions: ["mp4"]),
+        ]
+    }
+
+    func detectFileType(from data: Data) -> FileTypeSignature? {
+        let hexString = data.prefix(4).map { String(format: "%02X", $0) }.joined()
+        return FileTypeSignature.knownSignatures.first {
+            hexString.hasPrefix($0.magic)
+        }
+    }
+
+    func isLikelyTextFile(data: Data) -> Bool {
+        let sample = data.prefix(512)
+        for byte in sample {
+            if !(byte >= 0x09 && byte <= 0x0D) &&
+               !(byte >= 0x20 && byte <= 0x7E) {
+                return false
+            }
+        }
+        return true
+    }
+    
+    func isEncryptedPDF(data: Data) -> Bool {
+        if let content = String(data: data.prefix(2048), encoding: .ascii) {
+            return content.contains("/Encrypt")
+        }
+        return false
+    }
+
+    func isEncryptedOfficeFile(data: Data) -> Bool {
+        if let archive = Archive(data: data, accessMode: .read) {
+            if archive.contains(where: { $0.path.contains("EncryptedPackage") }) {
+                return true
+            }
+        }
+
+        // Check if it's OLE format (common for password-protected files)
+        let oleMagic = data.prefix(4).map { String(format: "%02X", $0) }.joined()
+        return oleMagic == "D0CF11E0"
+    }
+
+    func doesFileMatchExtension() -> Bool {
+        guard let fileExtension = self.pathExtension.lowercased().split(separator: "?").first,
+              let fileData = try? Data(contentsOf: self) else {
+            return false
+        }
+
+        if let detected = detectFileType(from: fileData) {
+            return detected.extensions.contains(String(fileExtension))
+        }
+
+        if isLikelyTextFile(data: fileData) {
+            return fileExtension == "txt"
+        }
+
+        return false
+    }
+
+    func validateFile() -> (matchesExtension: Bool, isEncrypted: Bool) {
+        guard let fileExtension = self.pathExtension.lowercased().split(separator: "?").first,
+              let fileData = try? Data(contentsOf: self) else {
+            return (false, false)
+        }
+
+        let matches = self.doesFileMatchExtension()
+        var isEncrypted = false
+
+        if matches {
+            if let type = detectFileType(from: fileData) {
+                if type.extensions.contains("pdf") {
+                    isEncrypted = isEncryptedPDF(data: fileData)
+                } else if type.extensions.contains(where: { ["pptx", "docx", "xlsx"].contains($0) }) {
+                    isEncrypted = isEncryptedOfficeFile(data: fileData)
+                }
+            }
+        }
+
+        return (matches, isEncrypted)
+    }
+    
 }
 
 extension UIColor {

+ 2 - 2
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -1379,8 +1379,8 @@ public class Nexilis: NSObject {
         return true
     }
     
-    public static func showLoader() {
-        loadingAlert = LibAlertController(title: nil, message: "Please wait...".localized(), preferredStyle: .alert)
+    public static func showLoader(text: String = "Please wait...".localized()) {
+        loadingAlert = LibAlertController(title: nil, message: text, preferredStyle: .alert)
 
         let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
         loadingIndicator.hidesWhenStopped = true

+ 45 - 17
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -2479,26 +2479,50 @@ extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPrevi
     }
     
     @objc private func sendDocument(sender: navigationQLPreviewDocument) {
-        sender.navigation.dismiss(animated: true, completion: nil)
-        do {
-            let dataFile = try Data(contentsOf: self.previewItem! as URL)
-            let urlFile = self.previewItem?.absoluteString
-            var originaFileName = (urlFile! as NSString).lastPathComponent
-            originaFileName = NSString(string: originaFileName).removingPercentEncoding!
-            let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_" + originaFileName
-            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-            let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
-            if !FileManager.default.fileExists(atPath: fileURL.path) {
+        DispatchQueue.global().async {
+            if Nexilis.checkingAccess(key: "content_inspection") {
+                DispatchQueue.main.async {
+                    Nexilis.showLoader(text: "Scanning File...".localized())
+                }
+                let result = (self.previewItem! as URL).validateFile()
+                DispatchQueue.main.async {
+                    Nexilis.hideLoader {
+                        sender.navigation.dismiss(animated: true, completion: {
+                            if result.matchesExtension {
+                                sendIt()
+                            } else {
+                                APIS.showWarningFile()
+                            }
+                        })
+                    }
+                }
+            } else {
+                DispatchQueue.main.async {
+                    sendIt()
+                }
+            }
+            
+            func sendIt() {
+                sender.navigation.dismiss(animated: true, completion: nil)
                 do {
-                    try dataFile.write(to: fileURL)
-                    //print("file saved")
+                    let dataFile = try Data(contentsOf: self.previewItem! as URL)
+                    let urlFile = self.previewItem?.absoluteString
+                    var originaFileName = (urlFile! as NSString).lastPathComponent
+                    originaFileName = NSString(string: originaFileName).removingPercentEncoding!
+                    let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_" + originaFileName
+                    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
+                    let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
+                    if !FileManager.default.fileExists(atPath: fileURL.path) {
+                        do {
+                            try dataFile.write(to: fileURL)
+                        } catch {
+                        }
+                    }
+                    self.sendChat(message_text: "\(originaFileName)|", attachment_flag: "6", file_id: renamedNameFile, viewController: self)
                 } catch {
-                    //print("error saving file:", error)
+                    
                 }
             }
-            sendChat(message_text: "\(originaFileName)|", attachment_flag: "6", file_id: renamedNameFile, viewController: self)
-        } catch {
-            
         }
     }
 }
@@ -2725,7 +2749,11 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
                 var nowHeightTableMention = heightTableMention!
                 if isEditingMessage {
                     nowTableMention = tableMentionEdit
-                    nowHeightTableMention = heightTableEditMention
+                    if heightTableEditMention != nil {
+                        nowHeightTableMention = heightTableEditMention
+                    } else {
+                        return
+                    }
                 }
                 if listMentionWithText.count > 0 {
                     if listMentionWithText.count < 5 {

+ 41 - 16
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -16,6 +16,7 @@ import SwiftLinkPreview
 import SDWebImage
 import PhotosUI
 import ObjectiveC
+import UniformTypeIdentifiers
 
 public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestureRecognizerDelegate, CLLocationManagerDelegate {
     @IBOutlet var wallpaperView: UIImageView!
@@ -3816,26 +3817,50 @@ extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPr
     }
     
     @objc private func sendDocument(sender: navigationQLPreviewDocument) {
-        sender.navigation.dismiss(animated: true, completion: nil)
-        do {
-            let dataFile = try Data(contentsOf: self.previewItem! as URL)
-            let urlFile = self.previewItem?.absoluteString
-            var originaFileName = (urlFile! as NSString).lastPathComponent
-            originaFileName = NSString(string: originaFileName).removingPercentEncoding!
-            let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_" + originaFileName
-            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-            let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
-            if !FileManager.default.fileExists(atPath: fileURL.path) {
+        DispatchQueue.global().async {
+            if Nexilis.checkingAccess(key: "content_inspection") {
+                DispatchQueue.main.async {
+                    Nexilis.showLoader(text: "Scanning File...".localized())
+                }
+                let result = (self.previewItem! as URL).validateFile()
+                DispatchQueue.main.async {
+                    Nexilis.hideLoader {
+                        sender.navigation.dismiss(animated: true, completion: {
+                            if result.matchesExtension {
+                                sendIt()
+                            } else {
+                                APIS.showWarningFile()
+                            }
+                        })
+                    }
+                }
+            } else {
+                DispatchQueue.main.async {
+                    sendIt()
+                }
+            }
+            
+            func sendIt() {
+                sender.navigation.dismiss(animated: true, completion: nil)
                 do {
-                    try dataFile.write(to: fileURL)
-                    //print("file saved")
+                    let dataFile = try Data(contentsOf: self.previewItem! as URL)
+                    let urlFile = self.previewItem?.absoluteString
+                    var originaFileName = (urlFile! as NSString).lastPathComponent
+                    originaFileName = NSString(string: originaFileName).removingPercentEncoding!
+                    let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_" + originaFileName
+                    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
+                    let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
+                    if !FileManager.default.fileExists(atPath: fileURL.path) {
+                        do {
+                            try dataFile.write(to: fileURL)
+                        } catch {
+                        }
+                    }
+                    self.sendChat(message_text: "\(originaFileName)|", attachment_flag: "6", file_id: renamedNameFile, viewController: self)
                 } catch {
-                    //print("error saving file:", error)
+                    
                 }
             }
-            sendChat(message_text: "\(originaFileName)|", attachment_flag: "6", file_id: renamedNameFile, viewController: self)
-        } catch {
-            
         }
     }
     

+ 6 - 0
NexilisLite/NexilisLite/Source/View/Control/ContactChatViewController.swift

@@ -48,6 +48,7 @@ class ContactChatViewController: UITableViewController {
     var noData = false
     
     var loadingData = true
+    var isGettingData = false
     var timerReloadData: Timer?
     
     var noUCList = false
@@ -388,6 +389,10 @@ class ContactChatViewController: UITableViewController {
     // MARK: - Data source
     
     func getData() {
+        if self.isGettingData {
+            return
+        }
+        self.isGettingData = true
         getChats {
             self.getContacts {
                 self.getGroups { g1 in
@@ -404,6 +409,7 @@ class ContactChatViewController: UITableViewController {
                     }
                     DispatchQueue.main.async {
                         self.tableView.reloadData()
+                        self.isGettingData = false
                     }
                 }
             }

+ 33 - 1
NexilisLite/NexilisLite/Source/View/Control/DocumentPicker.swift

@@ -41,7 +41,39 @@ open class DocumentPicker: NSObject {
     }
     
     public func present() {
-        let supportedTypes = [UTType.image, UTType.text, UTType.plainText, UTType.utf8PlainText, UTType.utf16ExternalPlainText, UTType.utf16PlainText,    UTType.delimitedText, UTType.commaSeparatedText, UTType.tabSeparatedText, UTType.utf8TabSeparatedText, UTType.rtf, UTType.pdf, UTType.webArchive, UTType.image, UTType.jpeg, UTType.tiff, UTType.gif, UTType.png, UTType.bmp, UTType.ico, UTType.rawImage, UTType.svg, UTType.livePhoto, UTType.movie, UTType.video, UTType.audio, UTType.quickTimeMovie, UTType.mpeg, UTType.mpeg2Video, UTType.mpeg2TransportStream, UTType.mp3, UTType.mpeg4Movie, UTType.mpeg4Audio, UTType.avi, UTType.aiff,    UTType.wav, UTType.midi, UTType.archive, UTType.gzip, UTType.bz2, UTType.zip, UTType.appleArchive, UTType.spreadsheet, UTType.epub, UTType.presentation]
+        var supportedTypes: [UTType] = [
+            // General text and images
+            .image, .text, .plainText, .utf8PlainText, .utf16ExternalPlainText, .utf16PlainText,
+            .delimitedText, .commaSeparatedText, .tabSeparatedText, .utf8TabSeparatedText,
+            .rtf, .pdf, .webArchive,
+            // Images
+            .jpeg, .tiff, .gif, .png, .bmp, .ico, .rawImage, .svg, .livePhoto,
+            // Media
+            .movie, .video, .audio, .quickTimeMovie, .mpeg, .mpeg2Video, .mpeg2TransportStream,
+            .mp3, .mpeg4Movie, .mpeg4Audio, .avi, .aiff, .wav, .midi,
+            // Archives
+            .archive, .gzip, .bz2, .zip, .appleArchive,
+            // Documents
+            .spreadsheet, .epub, .presentation,
+            // Code and script types
+            .sourceCode, .cSource, .objectiveCSource, .swiftSource, .cPlusPlusSource,
+            .objectiveCPlusPlusSource, .script, .shellScript, .pythonScript, .rubyScript,
+            .perlScript, .json, .xml, .html,
+            // Custom UTTypes via extension
+            UTType(filenameExtension: "java")!,     // Java
+            UTType(filenameExtension: "ts")!,       // TypeScript
+            UTType(filenameExtension: "yaml")!,     // YAML
+            UTType(filenameExtension: "yml")!,      // YAML
+            UTType(filenameExtension: "sql")!,      // SQL
+            UTType(filenameExtension: "csv")!,      // CSV
+            UTType(filenameExtension: "ini")!,      // INI
+            UTType(filenameExtension: "log")!,      // Log
+            UTType(filenameExtension: "js")!,       // JavaScript
+            UTType(filenameExtension: "md")!        // Markdown
+        ]
+        if #available(iOS 18.0, *){
+            supportedTypes.append(.css)
+        }
         self.pickerController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
 //        self.pickerController?.allowsMultipleSelection = true
         self.pickerController!.delegate = self