Browse Source

Merge pull request #2540 from nextcloud/develop

Version 4.8.6
Marino Faggiana 1 year ago
parent
commit
dc93e439cd
100 changed files with 570 additions and 158 deletions
  1. 2 2
      .swiftlint.yml
  2. 1 1
      Brand/Database.swift
  3. 2 1
      ExternalResources/NCApplicationHandle.swift
  4. 5 5
      Nextcloud.xcodeproj/project.pbxproj
  5. 34 5
      iOSClient/AppDelegate.swift
  6. 253 50
      iOSClient/Data/NCManageDatabase+Capabilities.swift
  7. 0 1
      iOSClient/Data/NCManageDatabase+E2EE.swift
  8. 0 1
      iOSClient/Data/NCManageDatabase+Groupfolders.swift
  9. 1 3
      iOSClient/Data/NCManageDatabase+Metadata.swift
  10. 32 0
      iOSClient/Data/NCManageDatabase+Share.swift
  11. 12 29
      iOSClient/Main/Collection Common/NCCollectionViewCommon.swift
  12. 20 14
      iOSClient/Main/NCPickerViewController.swift
  13. 8 6
      iOSClient/NCGlobal.swift
  14. 5 0
      iOSClient/Networking/E2EE/NCEndToEndEncryption.h
  15. 111 0
      iOSClient/Networking/E2EE/NCEndToEndEncryption.m
  16. 8 0
      iOSClient/Networking/E2EE/NCEndToEndMetadata.swift
  17. 7 4
      iOSClient/Networking/NCNetworkingChunkedUpload.swift
  18. 0 1
      iOSClient/Networking/NCService.swift
  19. 12 1
      iOSClient/Notification/NCNotification.swift
  20. 25 4
      iOSClient/Share/Advanced/NCShareCells.swift
  21. 5 0
      iOSClient/Share/NCShare+Helper.swift
  22. 2 2
      iOSClient/Share/NCShareNetworking.swift
  23. 22 27
      iOSClient/Shares/NCShares.swift
  24. BIN
      iOSClient/Supporting Files/af.lproj/Localizable.strings
  25. BIN
      iOSClient/Supporting Files/an.lproj/Localizable.strings
  26. BIN
      iOSClient/Supporting Files/ar.lproj/Localizable.strings
  27. BIN
      iOSClient/Supporting Files/ast.lproj/Localizable.strings
  28. BIN
      iOSClient/Supporting Files/az.lproj/Localizable.strings
  29. BIN
      iOSClient/Supporting Files/be.lproj/Localizable.strings
  30. BIN
      iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings
  31. BIN
      iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings
  32. BIN
      iOSClient/Supporting Files/br.lproj/Localizable.strings
  33. BIN
      iOSClient/Supporting Files/bs.lproj/Localizable.strings
  34. BIN
      iOSClient/Supporting Files/ca.lproj/Localizable.strings
  35. BIN
      iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings
  36. BIN
      iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings
  37. BIN
      iOSClient/Supporting Files/da.lproj/Localizable.strings
  38. BIN
      iOSClient/Supporting Files/de.lproj/Localizable.strings
  39. BIN
      iOSClient/Supporting Files/el.lproj/Localizable.strings
  40. BIN
      iOSClient/Supporting Files/en-GB.lproj/Localizable.strings
  41. 3 1
      iOSClient/Supporting Files/en.lproj/Localizable.strings
  42. BIN
      iOSClient/Supporting Files/eo.lproj/Localizable.strings
  43. BIN
      iOSClient/Supporting Files/es-419.lproj/Localizable.strings
  44. BIN
      iOSClient/Supporting Files/es-AR.lproj/Localizable.strings
  45. BIN
      iOSClient/Supporting Files/es-CL.lproj/Localizable.strings
  46. BIN
      iOSClient/Supporting Files/es-CO.lproj/Localizable.strings
  47. BIN
      iOSClient/Supporting Files/es-CR.lproj/Localizable.strings
  48. BIN
      iOSClient/Supporting Files/es-DO.lproj/Localizable.strings
  49. BIN
      iOSClient/Supporting Files/es-EC.lproj/Localizable.strings
  50. BIN
      iOSClient/Supporting Files/es-GT.lproj/Localizable.strings
  51. BIN
      iOSClient/Supporting Files/es-HN.lproj/Localizable.strings
  52. BIN
      iOSClient/Supporting Files/es-MX.lproj/Localizable.strings
  53. BIN
      iOSClient/Supporting Files/es-NI.lproj/Localizable.strings
  54. BIN
      iOSClient/Supporting Files/es-PA.lproj/Localizable.strings
  55. BIN
      iOSClient/Supporting Files/es-PE.lproj/Localizable.strings
  56. BIN
      iOSClient/Supporting Files/es-PR.lproj/Localizable.strings
  57. BIN
      iOSClient/Supporting Files/es-PY.lproj/Localizable.strings
  58. BIN
      iOSClient/Supporting Files/es-SV.lproj/Localizable.strings
  59. BIN
      iOSClient/Supporting Files/es-UY.lproj/Localizable.strings
  60. BIN
      iOSClient/Supporting Files/es.lproj/Localizable.strings
  61. BIN
      iOSClient/Supporting Files/et_EE.lproj/Localizable.strings
  62. BIN
      iOSClient/Supporting Files/eu.lproj/Localizable.strings
  63. BIN
      iOSClient/Supporting Files/fa.lproj/Localizable.strings
  64. BIN
      iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings
  65. BIN
      iOSClient/Supporting Files/fo.lproj/Localizable.strings
  66. BIN
      iOSClient/Supporting Files/fr.lproj/Localizable.strings
  67. BIN
      iOSClient/Supporting Files/gd.lproj/Localizable.strings
  68. BIN
      iOSClient/Supporting Files/gl.lproj/Localizable.strings
  69. BIN
      iOSClient/Supporting Files/he.lproj/Localizable.strings
  70. BIN
      iOSClient/Supporting Files/hi_IN.lproj/Localizable.strings
  71. BIN
      iOSClient/Supporting Files/hr.lproj/Localizable.strings
  72. BIN
      iOSClient/Supporting Files/hsb.lproj/Localizable.strings
  73. BIN
      iOSClient/Supporting Files/hu.lproj/Localizable.strings
  74. BIN
      iOSClient/Supporting Files/hy.lproj/Localizable.strings
  75. BIN
      iOSClient/Supporting Files/ia.lproj/Localizable.strings
  76. BIN
      iOSClient/Supporting Files/id.lproj/Localizable.strings
  77. BIN
      iOSClient/Supporting Files/ig.lproj/Localizable.strings
  78. BIN
      iOSClient/Supporting Files/is.lproj/Localizable.strings
  79. BIN
      iOSClient/Supporting Files/it.lproj/Localizable.strings
  80. BIN
      iOSClient/Supporting Files/ja-JP.lproj/Localizable.strings
  81. BIN
      iOSClient/Supporting Files/ka-GE.lproj/Localizable.strings
  82. BIN
      iOSClient/Supporting Files/ka.lproj/Localizable.strings
  83. BIN
      iOSClient/Supporting Files/kab.lproj/Localizable.strings
  84. BIN
      iOSClient/Supporting Files/km.lproj/Localizable.strings
  85. BIN
      iOSClient/Supporting Files/kn.lproj/Localizable.strings
  86. BIN
      iOSClient/Supporting Files/ko.lproj/Localizable.strings
  87. BIN
      iOSClient/Supporting Files/la.lproj/Localizable.strings
  88. BIN
      iOSClient/Supporting Files/lb.lproj/Localizable.strings
  89. BIN
      iOSClient/Supporting Files/lo.lproj/Localizable.strings
  90. BIN
      iOSClient/Supporting Files/lt_LT.lproj/Localizable.strings
  91. BIN
      iOSClient/Supporting Files/lv.lproj/Localizable.strings
  92. BIN
      iOSClient/Supporting Files/mk.lproj/Localizable.strings
  93. BIN
      iOSClient/Supporting Files/mn.lproj/Localizable.strings
  94. BIN
      iOSClient/Supporting Files/mr.lproj/Localizable.strings
  95. BIN
      iOSClient/Supporting Files/ms_MY.lproj/Localizable.strings
  96. BIN
      iOSClient/Supporting Files/my.lproj/Localizable.strings
  97. BIN
      iOSClient/Supporting Files/nb-NO.lproj/Localizable.strings
  98. BIN
      iOSClient/Supporting Files/ne.lproj/Localizable.strings
  99. BIN
      iOSClient/Supporting Files/nl.lproj/Localizable.strings
  100. BIN
      iOSClient/Supporting Files/nn_NO.lproj/Localizable.strings

+ 2 - 2
.swiftlint.yml

@@ -11,10 +11,10 @@ empty_count:
 
 line_length:
   warning: 400
-  error: 400
+  error: 5000
 
 function_body_length:
-   warning: 200
+   warning: 400
 
 type_body_length:
   warning: 800

+ 1 - 1
Brand/Database.swift

@@ -26,4 +26,4 @@ import Foundation
 // Database Realm
 //
 let databaseName                    = "nextcloud.realm"
-let databaseSchemaVersion: UInt64   = 299
+let databaseSchemaVersion: UInt64   = 301

+ 2 - 1
ExternalResources/NCApplicationHandle.swift

@@ -32,7 +32,8 @@ class NCApplicationHandle: NSObject {
 
     // class: AppDelegate
     // func nextcloudPushNotificationAction(data: [String: AnyObject])
-    func nextcloudPushNotificationAction(data: [String: AnyObject]) {
+    func nextcloudPushNotificationAction(data: [String: AnyObject], completion: @escaping () -> Void) {
+        completion()
     }
 
     // class: AppDelegate

+ 5 - 5
Nextcloud.xcodeproj/project.pbxproj

@@ -4566,7 +4566,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 4;
 				DEBUG_INFORMATION_FORMAT = dwarf;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4592,7 +4592,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.8.5;
+				MARKETING_VERSION = 4.8.6;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -4631,7 +4631,7 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				COPY_PHASE_STRIP = NO;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = 4;
 				DEVELOPMENT_TEAM = NKUJUXUJ3B;
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				ENABLE_TESTABILITY = YES;
@@ -4654,7 +4654,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 4.8.5;
+				MARKETING_VERSION = 4.8.6;
 				ONLY_ACTIVE_ARCH = YES;
 				OTHER_LDFLAGS = "";
 				SDKROOT = iphoneos;
@@ -4938,7 +4938,7 @@
 			repositoryURL = "https://github.com/nextcloud/NextcloudKit";
 			requirement = {
 				kind = exactVersion;
-				version = 2.6.0;
+				version = 2.7.0;
 			};
 		};
 		F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {

+ 34 - 5
iOSClient/AppDelegate.swift

@@ -230,9 +230,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         // Request TouchID, FaceID
         enableTouchFaceID()
         
-        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterApplicationWillEnterForeground)
         NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRichdocumentGrabFocus)
-        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetwork)
+        NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterReloadDataSourceNetwork, second: 2)
     }
 
     // L' applicazione si dimetterà dallo stato di attivo
@@ -440,7 +439,38 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     }
 
     func nextcloudPushNotificationAction(data: [String: AnyObject]) {
-        NCApplicationHandle().nextcloudPushNotificationAction(data: data)
+        NCApplicationHandle().nextcloudPushNotificationAction(data: data) {
+
+            var findAccount: Bool = false
+
+            if let accountPush = data["account"] as? String,
+               let app = data["app"] as? String,
+               app == NCGlobal.shared.twoFactorNotificatioName {
+                if accountPush == self.account {
+                    findAccount = true
+                } else {
+                    let accounts = NCManageDatabase.shared.getAllAccount()
+                    for account in accounts {
+                        if account.account == accountPush {
+                            self.changeAccount(account.account)
+                            findAccount = true
+                        }
+                    }
+                }
+                if findAccount, let viewController = UIStoryboard(name: "NCNotification", bundle: nil).instantiateInitialViewController() as? NCNotification {
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                        let navigationController = UINavigationController(rootViewController: viewController)
+                        navigationController.modalPresentationStyle = .fullScreen
+                        self.window?.rootViewController?.present(navigationController, animated: true)
+                    }
+                } else if !findAccount {
+                    let message = NSLocalizedString("_the_account_", comment: "") + " " + accountPush + " " + NSLocalizedString("_does_not_exist_", comment: "")
+                    let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
+                    alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
+                    self.window?.rootViewController?.present(alertController, animated: true, completion: { })
+                }
+            }
+        }
     }
 
     // MARK: - Login & checkErrorNetworking
@@ -845,8 +875,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
                 guard let userScheme = CCUtility.value(forKey: "user", fromQueryItems: queryItems) else { return false }
                 guard let urlScheme = CCUtility.value(forKey: "url", fromQueryItems: queryItems) else { return false }
                 if getMatchedAccount(userId: userScheme, url: urlScheme) == nil {
-                    let message = String(format: NSLocalizedString("_account_not_exists_", comment: ""), userScheme, urlScheme)
-
+                    let message = NSLocalizedString("_the_account_", comment: "") + " " + userScheme + NSLocalizedString("_of_", comment: "") + " " + urlScheme + " " + NSLocalizedString("_does_not_exist_", comment: "")
                     let alertController = UIAlertController(title: NSLocalizedString("_info_", comment: ""), message: message, preferredStyle: .alert)
                     alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in }))
 

+ 253 - 50
iOSClient/Data/NCManageDatabase+Capabilities.swift

@@ -24,7 +24,6 @@
 import Foundation
 import RealmSwift
 import NextcloudKit
-import SwiftyJSON
 
 class tableCapabilities: Object {
 
@@ -68,10 +67,204 @@ extension NCManageDatabase {
 
     func setCapabilities(account: String, data: Data? = nil) {
 
-        let json: JSON?
+        let jsonData: Data?
+
+        struct CapabilityNextcloud: Codable {
+
+            struct Ocs: Codable {
+                let meta: Meta
+                let data: Data
+
+                struct Meta: Codable {
+                    let status: String?
+                    let message: String?
+                    let statuscode: Int?
+                }
+
+                struct Data: Codable {
+                    let version: Version
+                    let capabilities: Capabilities
+
+                    struct Version: Codable {
+                        let string: String
+                        let major: Int
+                    }
+
+                    struct Capabilities: Codable {
+                        let filessharing: FilesSharing?
+                        let theming: Theming?
+                        let endtoendencryption: EndToEndEncryption?
+                        let richdocuments: RichDocuments?
+                        let activity: Activity?
+                        let notifications: Notifications?
+                        let files: Files?
+                        let userstatus: UserStatus?
+                        let external: External?
+                        let groupfolders: GroupFolders?
+
+                        enum CodingKeys: String, CodingKey {
+                            case filessharing = "files_sharing"
+                            case theming
+                            case endtoendencryption = "end-to-end-encryption"
+                            case richdocuments, activity, notifications, files
+                            case userstatus = "user_status"
+                            case external, groupfolders
+                        }
+
+                        struct FilesSharing: Codable {
+                            let apienabled: Bool?
+                            let groupsharing: Bool?
+                            let resharing: Bool?
+                            let defaultpermissions: Int?
+                            let ncpublic: Public?
+
+                            enum CodingKeys: String, CodingKey {
+                                case apienabled = "api_enabled"
+                                case groupsharing = "group_sharing"
+                                case resharing
+                                case defaultpermissions = "default_permissions"
+                                case ncpublic = "public"
+                            }
+
+                            struct Public: Codable {
+                                let upload: Bool
+                                let enabled: Bool
+                                let password: Password?
+                                let sendmail: Bool
+                                let uploadfilesdrop: Bool
+                                let multiplelinks: Bool
+                                let expiredate: ExpireDate?
+                                let expiredateinternal: ExpireDate?
+                                let expiredateremote: ExpireDate?
+
+                                enum CodingKeys: String, CodingKey {
+                                    case upload, enabled, password
+                                    case sendmail = "send_mail"
+                                    case uploadfilesdrop = "upload_files_drop"
+                                    case multiplelinks = "multiple_links"
+                                    case expiredate = "expire_date"
+                                    case expiredateinternal = "expire_date_internal"
+                                    case expiredateremote = "expire_date_remote"
+                                }
+
+                                struct Password: Codable {
+                                    let enforced: Bool?
+                                    let askForOptionalPassword: Bool?
+                                }
+
+                                struct ExpireDate: Codable {
+                                    let enforced: Bool?
+                                    let days: Int?
+                                }
+                            }
+                        }
+
+                        struct Theming: Codable {
+                            let color: String?
+                            let colorelement: String?
+                            let colortext: String?
+                            let colorelementbright: String?
+                            let backgrounddefault: Bool?
+                            let backgroundplain: Bool?
+                            let colorelementdark: String?
+                            let name: String?
+                            let slogan: String?
+                            let url: String?
+                            let logo: String?
+                            let background: String?
+                            let logoheader: String?
+                            let favicon: String?
+
+                            enum CodingKeys: String, CodingKey {
+                                case color
+                                case colorelement = "color-element"
+                                case colortext = "color-text"
+                                case colorelementbright = "color-element-bright"
+                                case backgrounddefault = "background-default"
+                                case backgroundplain = "background-plain"
+                                case colorelementdark = "color-element-dark"
+                                case name, slogan, url, logo, background, logoheader, favicon
+                            }
+                        }
+
+                        struct EndToEndEncryption: Codable {
+                            let enabled: Bool?
+                            let apiversion: String?
+                            let keysexist: Bool?
+
+                            enum CodingKeys: String, CodingKey {
+                                case enabled
+                                case apiversion = "api-version"
+                                case keysexist = "keys-exist"
+                            }
+                        }
+
+                        struct RichDocuments: Codable {
+                            let mimetypes: [String]?
+                        }
+
+                        struct Activity: Codable {
+                            let apiv2: [String]?
+                        }
+
+                        struct Notifications: Codable {
+                            let ocsendpoints: [String]?
+
+                            enum CodingKeys: String, CodingKey {
+                                case ocsendpoints = "ocs-endpoints"
+                            }
+                        }
+
+                        struct Files: Codable {
+                            let undelete: Bool?
+                            let locking: String?
+                            let comments: Bool?
+                            let versioning: Bool?
+                            let directEditing: DirectEditing?
+                            let bigfilechunking: Bool?
+                            let versiondeletion: Bool?
+                            let versionlabeling: Bool?
+
+                            enum CodingKeys: String, CodingKey {
+                                case undelete, locking, comments, versioning, directEditing, bigfilechunking
+                                case versiondeletion = "version_deletion"
+                                case versionlabeling = "version_labeling"
+                            }
+
+                            struct DirectEditing: Codable {
+                                let url: String?
+                                let etag: String?
+                                let supportsFileId: Bool?
+                            }
+                        }
+
+                        struct UserStatus: Codable {
+                            let enabled: Bool?
+                            let restore: Bool?
+                            let supportsemoji: Bool?
+
+                            enum CodingKeys: String, CodingKey {
+                                case enabled, restore
+                                case supportsemoji = "supports_emoji"
+                            }
+                        }
+
+                        struct External: Codable {
+                            let v1: [String]?
+                        }
+
+                        struct GroupFolders: Codable {
+                            let hasGroupFolders: Bool?
+                        }
+                    }
+                }
+            }
+
+            let ocs: Ocs
+        }
 
         if let data = data {
-            json = JSON(data)
+            jsonData = data
         } else {
             do {
                 let realm = try Realm()
@@ -79,61 +272,71 @@ extension NCManageDatabase {
                       let data = result.jsondata else {
                     return
                 }
-                json = JSON(data)
+                jsonData = data
             } catch let error as NSError {
-                NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
+                NextcloudKit.shared.nkCommonInstance.writeLog("I cannot access to database: \(error)")
                 return
             }
         }
+        guard let jsonData = jsonData else { return }
 
-        guard let json = json else { return }
-
-        NCGlobal.shared.capabilityServerVersion = json["ocs", "data", "version", "string"].stringValue
-        NCGlobal.shared.capabilityServerVersionMajor = json["ocs", "data", "version", "major"].intValue
-
-        NCGlobal.shared.capabilityFileSharingApiEnabled = json["ocs", "data", "capabilities", "files_sharing", "api_enabled"].boolValue
-        NCGlobal.shared.capabilityFileSharingPubPasswdEnforced = json["ocs", "data", "capabilities", "files_sharing", "public", "password", "enforced"].boolValue
-        NCGlobal.shared.capabilityFileSharingPubExpireDateEnforced = json["ocs", "data", "capabilities", "files_sharing", "public", "expire_date", "enforced"].boolValue
-        NCGlobal.shared.capabilityFileSharingPubExpireDateDays = json["ocs", "data", "capabilities", "files_sharing", "public", "expire_date", "days"].intValue
-        NCGlobal.shared.capabilityFileSharingInternalExpireDateEnforced = json["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_internal", "enforced"].boolValue
-        NCGlobal.shared.capabilityFileSharingInternalExpireDateDays = json["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_internal", "days"].intValue
-        NCGlobal.shared.capabilityFileSharingRemoteExpireDateEnforced = json["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_remote", "enforced"].boolValue
-        NCGlobal.shared.capabilityFileSharingRemoteExpireDateDays = json["ocs", "data", "capabilities", "files_sharing", "public", "expire_date_remote", "days"].intValue
-        NCGlobal.shared.capabilityFileSharingDefaultPermission = json["ocs", "data", "capabilities", "files_sharing", "default_permissions"].intValue
-
-        NCGlobal.shared.capabilityThemingColor = json["ocs", "data", "capabilities", "theming", "color"].stringValue
-        NCGlobal.shared.capabilityThemingColorElement = json["ocs", "data", "capabilities", "theming", "color-element"].stringValue
-        NCGlobal.shared.capabilityThemingColorText = json["ocs", "data", "capabilities", "theming", "color-text"].stringValue
-        NCGlobal.shared.capabilityThemingName = json["ocs", "data", "capabilities", "theming", "name"].stringValue
-        NCGlobal.shared.capabilityThemingSlogan = json["ocs", "data", "capabilities", "theming", "slogan"].stringValue
-
-        NCGlobal.shared.capabilityE2EEEnabled = json["ocs", "data", "capabilities", "end-to-end-encryption", "enabled"].boolValue
-        NCGlobal.shared.capabilityE2EEApiVersion = json["ocs", "data", "capabilities", "end-to-end-encryption", "api-version"].stringValue
-
-        NCGlobal.shared.capabilityRichdocumentsMimetypes.removeAll()
-        let mimetypes = json["ocs", "data", "capabilities", "richdocuments", "mimetypes"].arrayValue
-        for mimetype in mimetypes {
-            NCGlobal.shared.capabilityRichdocumentsMimetypes.append(mimetype.stringValue)
-        }
+        do {
+            let json = try JSONDecoder().decode(CapabilityNextcloud.self, from: jsonData)
+            NCGlobal.shared.capabilityServerVersion = json.ocs.data.version.string
+            NCGlobal.shared.capabilityServerVersionMajor = json.ocs.data.version.major
 
-        NCGlobal.shared.capabilityActivity.removeAll()
-        let activities = json["ocs", "data", "capabilities", "activity", "apiv2"].arrayValue
-        for activity in activities {
-            NCGlobal.shared.capabilityActivity.append(activity.stringValue)
-        }
+            NCGlobal.shared.capabilityFileSharingApiEnabled = json.ocs.data.capabilities.filessharing?.apienabled ?? false
+            NCGlobal.shared.capabilityFileSharingDefaultPermission = json.ocs.data.capabilities.filessharing?.defaultpermissions ?? 0
+            NCGlobal.shared.capabilityFileSharingPubPasswdEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.password?.enforced ?? false
+            NCGlobal.shared.capabilityFileSharingPubExpireDateEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredate?.enforced ?? false
+            NCGlobal.shared.capabilityFileSharingPubExpireDateDays = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredate?.days ?? 0
+            NCGlobal.shared.capabilityFileSharingInternalExpireDateEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateinternal?.enforced ?? false
+            NCGlobal.shared.capabilityFileSharingInternalExpireDateDays = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateinternal?.days ?? 0
+            NCGlobal.shared.capabilityFileSharingRemoteExpireDateEnforced = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateremote?.enforced ?? false
+            NCGlobal.shared.capabilityFileSharingRemoteExpireDateDays = json.ocs.data.capabilities.filessharing?.ncpublic?.expiredateremote?.days ?? 0
 
-        NCGlobal.shared.capabilityNotification.removeAll()
-        let notifications = json["ocs", "data", "capabilities", "notifications", "ocs-endpoints"].arrayValue
-        for notify in notifications {
-            NCGlobal.shared.capabilityNotification.append(notify.stringValue)
-        }
+            NCGlobal.shared.capabilityThemingColor = json.ocs.data.capabilities.theming?.color ?? ""
+            NCGlobal.shared.capabilityThemingColorElement = json.ocs.data.capabilities.theming?.colorelement ?? ""
+            NCGlobal.shared.capabilityThemingColorText = json.ocs.data.capabilities.theming?.colortext ?? ""
+            NCGlobal.shared.capabilityThemingName = json.ocs.data.capabilities.theming?.name ?? ""
+            NCGlobal.shared.capabilityThemingSlogan = json.ocs.data.capabilities.theming?.slogan ?? ""
 
-        NCGlobal.shared.capabilityFilesUndelete = json["ocs", "data", "capabilities", "files", "undelete"].boolValue
-        NCGlobal.shared.capabilityFilesLockVersion = json["ocs", "data", "capabilities", "files", "locking"].stringValue
-        NCGlobal.shared.capabilityFilesComments = json["ocs", "data", "capabilities", "files", "comments"].boolValue
+            NCGlobal.shared.capabilityE2EEEnabled = json.ocs.data.capabilities.endtoendencryption?.enabled ?? false
+            NCGlobal.shared.capabilityE2EEApiVersion = json.ocs.data.capabilities.endtoendencryption?.apiversion ?? ""
 
-        NCGlobal.shared.capabilityUserStatusEnabled = json["ocs", "data", "capabilities", "user_status", "enabled"].boolValue
-        NCGlobal.shared.capabilityExternalSites = json["ocs", "data", "capabilities", "external"].exists()
-        NCGlobal.shared.capabilityGroupfoldersEnabled = json["ocs", "data", "capabilities", "groupfolders", "hasGroupFolders"].boolValue
+            NCGlobal.shared.capabilityRichdocumentsMimetypes.removeAll()
+            if let mimetypes = json.ocs.data.capabilities.richdocuments?.mimetypes {
+                for mimetype in mimetypes {
+                    NCGlobal.shared.capabilityRichdocumentsMimetypes.append(mimetype)
+                }
+            }
+
+            NCGlobal.shared.capabilityActivity.removeAll()
+            if let activities = json.ocs.data.capabilities.activity?.apiv2 {
+                for activity in activities {
+                    NCGlobal.shared.capabilityActivity.append(activity)
+                }
+            }
+
+            NCGlobal.shared.capabilityNotification.removeAll()
+            if let notifications = json.ocs.data.capabilities.notifications?.ocsendpoints {
+                for notification in notifications {
+                    NCGlobal.shared.capabilityNotification.append(notification)
+                }
+            }
+
+            NCGlobal.shared.capabilityFilesUndelete = json.ocs.data.capabilities.files?.undelete ?? false
+            NCGlobal.shared.capabilityFilesLockVersion = json.ocs.data.capabilities.files?.locking ?? ""
+            NCGlobal.shared.capabilityFilesComments = json.ocs.data.capabilities.files?.comments ?? false
+
+            NCGlobal.shared.capabilityUserStatusEnabled = json.ocs.data.capabilities.files?.undelete ?? false
+            if json.ocs.data.capabilities.external != nil {
+                NCGlobal.shared.capabilityExternalSites = true
+            }
+            NCGlobal.shared.capabilityGroupfoldersEnabled = json.ocs.data.capabilities.groupfolders?.hasGroupFolders ?? false
+        } catch let error as NSError {
+            NextcloudKit.shared.nkCommonInstance.writeLog("I cannot access to database: \(error)")
+            return
+        }
     }
 }

+ 0 - 1
iOSClient/Data/NCManageDatabase+E2EE.swift

@@ -24,7 +24,6 @@
 import Foundation
 import RealmSwift
 import NextcloudKit
-import SwiftyJSON
 
 class tableE2eEncryption: Object {
 

+ 0 - 1
iOSClient/Data/NCManageDatabase+Groupfolders.swift

@@ -24,7 +24,6 @@
 import Foundation
 import RealmSwift
 import NextcloudKit
-import SwiftyJSON
 
 class TableGroupfolders: Object {
 

+ 1 - 3
iOSClient/Data/NCManageDatabase+Metadata.swift

@@ -464,9 +464,7 @@ extension NCManageDatabase {
         do {
             let realm = try Realm()
             try realm.write {
-                for metadata in metadatas {
-                    realm.add(metadata, update: .all)
-                }
+                realm.add(metadatas, update: .all)
             }
         } catch let error {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")

+ 32 - 0
iOSClient/Data/NCManageDatabase+Share.swift

@@ -67,6 +67,7 @@ class tableShareV2: Object {
     @objc dynamic var userIcon = ""
     @objc dynamic var userMessage = ""
     @objc dynamic var userStatus = ""
+    @objc dynamic var attributes: String?
 
     override static func primaryKey() -> String {
         return "primaryKey"
@@ -80,6 +81,8 @@ extension NCManageDatabase {
         do {
             let realm = try Realm()
             try realm.write {
+                let result = realm.objects(tableShare.self).filter("account == %@", account)
+                realm.delete(result)
                 for share in shares {
                     let serverUrlPath = home + share.path
                     guard let serverUrl = NCUtilityFileSystem.shared.deleteLastPath(serverUrlPath: serverUrlPath, home: home) else { continue }
@@ -125,6 +128,7 @@ extension NCManageDatabase {
                     object.userIcon = share.userIcon
                     object.userMessage = share.userMessage
                     object.userStatus = share.userStatus
+                    object.attributes = share.attributes
                     realm.add(object, update: .all)
                 }
             }
@@ -234,4 +238,32 @@ extension NCManageDatabase {
             NextcloudKit.shared.nkCommonInstance.writeLog("Could not write to database: \(error)")
         }
     }
+
+    // There is currently only one share attribute “download” from the scope “permissions”. This attribute is only valid for user and group shares, not for public link shares.
+    func setAttibuteDownload(state: Bool) -> String? {
+        if state {
+            return nil
+        } else {
+            return "[{\"scope\":\"permissions\",\"key\":\"download\",\"enabled\":false}]"
+        }
+    }
+
+    func isAttributeDownloadEnabled(attributes: String?) -> Bool {
+        if let attributes = attributes, let data = attributes.data(using: .utf8) {
+            do {
+                if let json = try JSONSerialization.jsonObject(with: data) as? [Dictionary<String, Any>] {
+                    for sub in json {
+                        let key = sub["key"] as? String
+                        let enabled = sub["enabled"] as? Bool
+                        let scope = sub["scope"] as? String
+                        if key == "download", scope == "permissions", let enabled = enabled {
+                            return enabled
+                        }
+                    }
+                }
+            } catch let error as NSError { print(error) }
+        }
+        return true
+    }
+
 }

+ 12 - 29
iOSClient/Main/Collection Common/NCCollectionViewCommon.swift

@@ -445,23 +445,13 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     @objc func downloadedFile(_ notification: NSNotification) {
 
         guard let userInfo = notification.userInfo as NSDictionary?,
-              let ocId = userInfo["ocId"] as? String,
               let serverUrl = userInfo["serverUrl"] as? String,
               serverUrl == self.serverUrl,
               let account = userInfo["account"] as? String,
               account == appDelegate.account
         else { return }
 
-        let (indexPath, sameSections) = dataSource.reloadMetadata(ocId: ocId)
-        if let indexPath = indexPath {
-            if sameSections && (indexPath.section < collectionView.numberOfSections && indexPath.row < collectionView.numberOfItems(inSection: indexPath.section)) {
-                collectionView?.reloadItems(at: [indexPath])
-            } else {
-                self.collectionView?.reloadData()
-            }
-        } else {
-            reloadDataSource()
-        }
+        reloadDataSource()
     }
 
     @objc func downloadCancelFile(_ notification: NSNotification) {
@@ -874,14 +864,8 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
     func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
 
         return UIContextMenuConfiguration(identifier: nil, previewProvider: {
-
             return nil
-
         }, actionProvider: { _ in
-
-            // let share = UIAction(title: "Share Pupper", image: UIImage(systemName: "square.and.arrow.up")) { action in
-            // }
-            // return UIMenu(title: "Main Menu", children: [share])
             return nil
         })
     }
@@ -1004,13 +988,12 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS
 
     func unifiedSearchMore(metadataForSection: NCMetadataForSection?) {
 
-        guard let metadataForSection = metadataForSection, let searchResult = metadataForSection.lastSearchResult, let cursor = searchResult.cursor, let term = literalSearch else { return }
+        guard let metadataForSection = metadataForSection, let lastSearchResult = metadataForSection.lastSearchResult, let cursor = lastSearchResult.cursor, let term = literalSearch else { return }
 
         metadataForSection.unifiedSearchInProgress = true
         self.collectionView?.reloadData()
 
-        NCNetworking.shared.unifiedSearchFilesProvider(userBaseUrl: appDelegate, id: searchResult.id, term: term, limit: 5, cursor: cursor) { account, searchResult, metadatas, error in
-
+        NCNetworking.shared.unifiedSearchFilesProvider(userBaseUrl: appDelegate, id: lastSearchResult.id, term: term, limit: 5, cursor: cursor) { account, searchResult, metadatas, error in
             if error != .success {
                 NCContentPresenter.shared.showError(error: error)
             }
@@ -1377,14 +1360,6 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
                         NCOperationQueue.shared.downloadAvatar(user: ownerId, dispalyName: nil, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.filePreviewImageView)
                     }
                 }
-
-//                if metadata.iconName.contains("contacts"), let subline = metadata.subline, !subline.isEmpty, let cell = cell as? NCCellProtocol {
-//                    let components = subline.components(separatedBy: "@")
-//                    if let user = components.first {
-//                        let fileName = metadata.userBaseUrl + "-" + user + ".png"
-//                        NCOperationQueue.shared.downloadAvatar(user: user, dispalyName: nil, fileName: fileName, cell: cell, view: collectionView, cellImageView: cell.filePreviewImageView)
-//                    }
-//                }
             }
         }
 
@@ -1723,13 +1698,21 @@ extension NCCollectionViewCommon: UICollectionViewDataSource {
             footer.buttonIsHidden(true)
             footer.hideActivityIndicatorSection()
 
+
             if isSearchingMode {
                 if sections > 1 && section != sections - 1 {
                     footer.separatorIsHidden(false)
                 }
-                if isSearchingMode && isPaginated && metadatasCount > 0 {
+
+                // If the number of entries(metadatas) is lower than the cursor, then there are no more entries.
+                // The blind spot in this is when the number of entries is the same as the cursor. If so, we don't have a way of knowing if there are no more entries.
+                // This is as good as it gets for determining last page without server-side flag.
+                let isLastPage = (metadatasCount < metadataForSection?.lastSearchResult?.cursor ?? 0) || metadataForSection?.lastSearchResult?.entries.isEmpty == true
+
+                if isSearchingMode && isPaginated && metadatasCount > 0 && !isLastPage {
                     footer.buttonIsHidden(false)
                 }
+
                 if unifiedSearchInProgress {
                     footer.showActivityIndicatorSection()
                 }

+ 20 - 14
iOSClient/Main/NCPickerViewController.swift

@@ -138,13 +138,11 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
     }
 
     func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
-
-        let ocId = NSUUID().uuidString
-
         if isViewerMedia,
             let urlIn = urls.first,
             let url = self.copySecurityScopedResource(url: urlIn, urlOut: FileManager.default.temporaryDirectory.appendingPathComponent(urlIn.lastPathComponent)),
             let viewController = self.viewController {
+            let ocId = NSUUID().uuidString
 
             let fileName = url.lastPathComponent
             let metadata = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: ocId, serverUrl: "", urlBase: appDelegate.urlBase, url: url.path, contentType: "")
@@ -155,15 +153,19 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
             NCViewer.shared.view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil)
 
         } else {
+            let serverUrl = appDelegate.activeServerUrl
+
+            var metadatas = [tableMetadata]()
+            var metadatasInConflict = [tableMetadata]()
 
             for urlIn in urls {
+                let ocId = NSUUID().uuidString
 
                 let fileName = urlIn.lastPathComponent
                 let toPath = CCUtility.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName)!
                 let urlOut = URL(fileURLWithPath: toPath)
-                let serverUrl = appDelegate.activeServerUrl
 
-                guard let url = self.copySecurityScopedResource(url: urlIn, urlOut: urlOut) else { continue }
+                guard let _ = self.copySecurityScopedResource(url: urlIn, urlOut: urlOut) else { continue }
 
                 let metadataForUpload = NCManageDatabase.shared.createMetadata(account: appDelegate.account, user: appDelegate.user, userId: appDelegate.userId, fileName: fileName, fileNameView: fileName, ocId: ocId, serverUrl: serverUrl, urlBase: appDelegate.urlBase, url: "", contentType: "")
 
@@ -172,19 +174,23 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate {
                 metadataForUpload.size = NCUtilityFileSystem.shared.getFileSize(filePath: toPath)
                 metadataForUpload.status = NCGlobal.shared.metadataStatusWaitUpload
 
-                if NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: fileName) != nil {
+                if let _ = NCManageDatabase.shared.getMetadataConflict(account: appDelegate.account, serverUrl: serverUrl, fileNameView: fileName) {
+                    metadatasInConflict.append(metadataForUpload)
+                } else {
+                    metadatas.append(metadataForUpload)
+                }
+            }
 
-                    if let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict {
+            NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: metadatas, completion: { _ in })
 
-                        conflict.delegate = appDelegate
-                        conflict.serverUrl = serverUrl
-                        conflict.metadatasUploadInConflict = [metadataForUpload]
+            if !metadatasInConflict.isEmpty {
+                if let conflict = UIStoryboard(name: "NCCreateFormUploadConflict", bundle: nil).instantiateInitialViewController() as? NCCreateFormUploadConflict {
 
-                        appDelegate.window?.rootViewController?.present(conflict, animated: true, completion: nil)
-                    }
+                    conflict.delegate = appDelegate
+                    conflict.serverUrl = serverUrl
+                    conflict.metadatasUploadInConflict = metadatasInConflict
 
-                } else {
-                    NCNetworkingProcessUpload.shared.createProcessUploads(metadatas: [metadataForUpload], completion: { _ in })
+                    appDelegate.window?.rootViewController?.present(conflict, animated: true, completion: nil)
                 }
             }
         }

+ 8 - 6
iOSClient/NCGlobal.swift

@@ -22,7 +22,6 @@
 //
 
 import UIKit
-import SwiftyJSON
 
 class NCGlobal: NSObject {
     @objc static let shared: NCGlobal = {
@@ -86,11 +85,13 @@ class NCGlobal: NSObject {
     let refreshTask                                 = "com.nextcloud.refreshTask"
     let processingTask                              = "com.nextcloud.processingTask"
 
-    // Name
+    // App
     //
-    @objc let appName                               = "files"
-    @objc let talkName                              = "talk-message"
-    @objc let appScheme                             = "nextcloud"
+    let appName                                     = "files"
+    let appScheme                                   = "nextcloud"
+    let talkName                                    = "talk-message"
+    let spreedName                                  = "spreed"
+    let twoFactorNotificatioName                    = "twofactor_nextcloud_notification"
 
     // Nextcloud version
     //
@@ -255,6 +256,8 @@ class NCGlobal: NSObject {
     @objc let permissionMaxFolderShare: Int         = 31
     @objc let permissionDefaultFileRemoteShareNoSupportShareOption: Int     = 3
     @objc let permissionDefaultFolderRemoteShareNoSupportShareOption: Int   = 15
+    // ATTRIBUTES
+    @objc let permissionDownloadShare: Int          = 0
 
     // Filename Mask and Type
     //
@@ -319,7 +322,6 @@ class NCGlobal: NSObject {
     // Notification Center
     //
     @objc let notificationCenterApplicationDidEnterBackground   = "applicationDidEnterBackground"
-    let notificationCenterApplicationWillEnterForeground        = "applicationWillEnterForeground"
     let notificationCenterApplicationDidBecomeActive            = "applicationDidBecomeActive"
     let notificationCenterApplicationWillResignActive           = "applicationWillResignActive"
 

+ 5 - 0
iOSClient/Networking/E2EE/NCEndToEndEncryption.h

@@ -56,6 +56,11 @@
 - (BOOL)encryptFile:(NSString *)fileName fileNameIdentifier:(NSString *)fileNameIdentifier directory:(NSString *)directory key:(NSString **)key initializationVector:(NSString **)initializationVector authenticationTag:(NSString **)authenticationTag;
 - (BOOL)decryptFile:(NSString *)fileName fileNameView:(NSString *)fileNameView ocId:(NSString *)ocId key:(NSString *)key initializationVector:(NSString *)initializationVector authenticationTag:(NSString *)authenticationTag;
 
+// Signature CMS
+
+- (NSData *)generateSignatureCMS:(NSData *)data certificate:(NSString *)certificate privateKey:(NSString *)privateKey publicKey:(NSString *)publicKey userId:(NSString *)userId;
+- (BOOL)verifySignatureCMS:(NSData *)cmsContent data:(NSData *)data publicKey:(NSString *)publicKey userId:(NSString *)userId;
+
 // Utility
 
 - (void)Encodedkey:(NSString **)key initializationVector:(NSString **)initializationVector;

+ 111 - 0
iOSClient/Networking/E2EE/NCEndToEndEncryption.m

@@ -1114,6 +1114,117 @@
     return status; // OpenSSL uses 1 for success
 }
 
+#
+#pragma mark - CMS
+#
+
+- (NSData *)generateSignatureCMS:(NSData *)data certificate:(NSString *)certificate privateKey:(NSString *)privateKey publicKey:(NSString *)publicKey userId:(NSString *)userId
+{
+    unsigned char *pKey = (unsigned char *)[privateKey UTF8String];
+    unsigned char *certKey = (unsigned char *)[certificate UTF8String];
+    BIO *printBIO = BIO_new_fp(stdout, BIO_NOCLOSE);
+
+    BIO *certKeyBIO = BIO_new_mem_buf(certKey, -1);
+    if (!certKeyBIO)
+        return nil;
+
+    X509 *x509 = PEM_read_bio_X509(certKeyBIO, NULL, 0, NULL);
+    if (!x509)
+        return nil;
+
+    BIO *pkeyBIO = BIO_new_mem_buf(pKey, -1);
+    EVP_PKEY *key = PEM_read_bio_PrivateKey(pkeyBIO, NULL, NULL, NULL);
+    if (!key)
+        return nil;
+
+    BIO *dataBIO = BIO_new_mem_buf((void*)data.bytes, (int)data.length);
+
+    CMS_ContentInfo *contentInfo = CMS_sign(x509, key, NULL, dataBIO, CMS_DETACHED);
+    if (contentInfo == nil)
+        return nil;
+
+    CMS_ContentInfo_print_ctx(printBIO, contentInfo, 0, NULL);
+    PEM_write_bio_CMS(printBIO, contentInfo);
+
+    BIO *i2dCmsBioOut = BIO_new(BIO_s_mem());
+    if (i2d_CMS_bio(i2dCmsBioOut, contentInfo) != 1)
+        return nil;
+
+    int len = BIO_pending(i2dCmsBioOut);
+    char *keyBytes = malloc(len);
+    BIO_read(i2dCmsBioOut, keyBytes, len);
+
+    NSData *i2dCmsData = [NSData dataWithBytes:keyBytes length:len];
+
+    // TEST
+    [self verifySignatureCMS:i2dCmsData data:data publicKey:publicKey userId:userId];
+
+    BIO_free(printBIO);
+    BIO_free(certKeyBIO);
+    BIO_free(pkeyBIO);
+    BIO_free(dataBIO);
+    BIO_free(i2dCmsBioOut);
+
+    return i2dCmsData;
+}
+
+- (BOOL)verifySignatureCMS:(NSData *)cmsContent data:(NSData *)data publicKey:(NSString *)publicKey userId:(NSString *)userId
+{
+    BIO *dataBIO = BIO_new_mem_buf((void*)data.bytes, (int)data.length);
+    BIO *printBIO = BIO_new_fp(stdout, BIO_NOCLOSE);
+    BIO *cmsBIO = BIO_new_mem_buf(cmsContent.bytes, (int)cmsContent.length);
+
+    CMS_ContentInfo *contentInfo = d2i_CMS_bio(cmsBIO, NULL);
+
+    unsigned char *publicKeyUTF8 = (unsigned char *)[publicKey UTF8String];
+    BIO *publicKeyBIO = BIO_new_mem_buf(publicKeyUTF8, -1);
+    EVP_PKEY *pkey = PEM_read_bio_PUBKEY(publicKeyBIO, NULL, NULL, NULL);
+
+    CMS_ContentInfo_print_ctx(printBIO, contentInfo, 0, NULL);
+
+    BOOL verifyResult = CMS_verify(contentInfo, NULL, NULL, dataBIO, NULL, CMS_DETACHED | CMS_NO_SIGNER_CERT_VERIFY);
+
+    if (verifyResult) {
+
+        STACK_OF(X509) *signers = CMS_get0_signers(contentInfo);
+        int numSigners = sk_X509_num(signers);
+
+        for (int i = 0; i < numSigners; ++i) {
+
+            X509 *signer = sk_X509_value(signers, i);
+            int result = X509_verify(signer, pkey);
+            if (result <= 0) {
+                verifyResult = false;
+                break;
+            }
+
+            int cnDataLength = X509_NAME_get_text_by_NID(X509_get_subject_name(signer), NID_commonName, 0, 0);
+            cnDataLength += 1;
+            NSMutableData* cnData = [NSMutableData dataWithLength:cnDataLength];
+            X509_NAME_get_text_by_NID(X509_get_subject_name(signer), NID_commonName, [cnData mutableBytes], cnDataLength);
+            NSString *cn = [[NSString alloc] initWithCString:[cnData mutableBytes] encoding:NSUTF8StringEncoding];
+            if ([userId isEqualToString:cn]) {
+                verifyResult = true;
+                break;
+            } else {
+                verifyResult = false;
+            }
+        }
+
+        if (signers) {
+            sk_X509_free(signers);
+        }
+        signers = NULL;
+    }
+
+    BIO_free(dataBIO);
+    BIO_free(printBIO);
+    BIO_free(cmsBIO);
+    BIO_free(publicKeyBIO);
+
+    return verifyResult;
+}
+
 #
 #pragma mark - Utility
 #

File diff suppressed because it is too large
+ 8 - 0
iOSClient/Networking/E2EE/NCEndToEndMetadata.swift


+ 7 - 4
iOSClient/Networking/NCNetworkingChunkedUpload.swift

@@ -144,11 +144,14 @@ extension NCNetworking {
             let serverUrlFileNameDestination = metadata.urlBase + "/" + NextcloudKit.shared.nkCommonInstance.dav + "/files/" + metadata.userId + pathServerUrl + "/" + metadata.fileName
 
             var customHeader: [String: String] = [:]
-            let creationDate = "\(metadata.creationDate.timeIntervalSince1970)"
-            let modificationDate = "\(metadata.date.timeIntervalSince1970)"
 
-            customHeader["X-OC-CTime"] = creationDate
-            customHeader["X-OC-MTime"] = modificationDate
+            if metadata.creationDate.timeIntervalSince1970 > 0 {
+                customHeader["X-OC-CTime"] = "\(metadata.creationDate.timeIntervalSince1970)"
+            }
+
+            if metadata.date.timeIntervalSince1970 > 0 {
+                customHeader["X-OC-MTime"] = "\(metadata.date.timeIntervalSince1970)"
+            }
 
             // Calculate Assemble Timeout
             let ASSEMBLE_TIME_PER_GB: Double    = 3 * 60            // 3  min

+ 0 - 1
iOSClient/Networking/NCService.swift

@@ -24,7 +24,6 @@
 import UIKit
 import SVGKit
 import NextcloudKit
-import SwiftyJSON
 
 class NCService: NSObject {
     @objc static let shared: NCService = {

+ 12 - 1
iOSClient/Notification/NCNotification.swift

@@ -49,6 +49,13 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate, NCEmpty
 
         refreshControl?.addTarget(self, action: #selector(getNetwokingNotification), for: .valueChanged)
 
+        // Navigation controller is being presented modally
+        if navigationController?.presentingViewController != nil {
+            navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .plain, action: { [weak self] in
+                self?.dismiss(animated: true)
+            })
+        }
+
         // Empty
         let offset = (self.navigationController?.navigationBar.bounds.height ?? 0) - 20
         emptyDataSet = NCEmptyDataSet(view: tableView, offset: -offset, delegate: self)
@@ -258,7 +265,7 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate, NCEmpty
     }
 
     func tapAction(with notification: NKNotifications, label: String) {
-        if notification.app == "spreed",
+        if notification.app == NCGlobal.shared.spreedName,
            let roomToken = notification.objectId.split(separator: "/").first,
            let talkUrl = URL(string: "nextcloudtalk://open-conversation?server=\(appDelegate.urlBase)&user=\(appDelegate.userId)&withRoomToken=\(roomToken)"),
            UIApplication.shared.canOpenURL(talkUrl) {
@@ -280,11 +287,15 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate, NCEmpty
                         self.notifications.remove(at: index)
                     }
                     self.tableView.reloadData()
+                    if self.navigationController?.presentingViewController != nil, notification.app == NCGlobal.shared.twoFactorNotificatioName {
+                        self.dismiss(animated: true)
+                    }
                 } else if error != .success {
                     NCContentPresenter.shared.showError(error: error)
                 } else {
                     print("[Error] The user has been changed during networking process.")
                 }
+
             }
         } // else: Action not found
     }

+ 25 - 4
iOSClient/Share/Advanced/NCShareCells.swift

@@ -49,31 +49,46 @@ protocol NCPermission: NCToggleCellConfig {
     static var forDirectoryE2EE: [Self] { get }
     static var forFile: [Self] { get }
     func hasResharePermission(for parentPermission: Int) -> Bool
+    func hasDownload() -> Bool
 }
 
 enum NCUserPermission: CaseIterable, NCPermission {
     func hasResharePermission(for parentPermission: Int) -> Bool {
+        if self == .download { return true }
         return ((permissionBitFlag & parentPermission) != 0)
     }
 
+    func hasDownload() -> Bool {
+        return self == .download
+    }
+
     var permissionBitFlag: Int {
         switch self {
         case .reshare: return NCGlobal.shared.permissionShareShare
         case .edit: return NCGlobal.shared.permissionUpdateShare
         case .create: return NCGlobal.shared.permissionCreateShare
         case .delete: return NCGlobal.shared.permissionDeleteShare
+        case .download: return NCGlobal.shared.permissionDownloadShare
         }
     }
 
     func didChange(_ share: NCTableShareable, to newValue: Bool) {
-        share.permissions ^= permissionBitFlag
+        if self == .download {
+            share.attributes = NCManageDatabase.shared.setAttibuteDownload(state: newValue)
+        } else {
+            share.permissions ^= permissionBitFlag
+        }
     }
 
     func isOn(for share: NCTableShareable) -> Bool {
-        return (share.permissions & permissionBitFlag) != 0
+        if self == .download {
+            return NCManageDatabase.shared.isAttributeDownloadEnabled(attributes: share.attributes)
+        } else {
+            return (share.permissions & permissionBitFlag) != 0
+        }
     }
 
-    case reshare, edit, create, delete
+    case reshare, edit, create, delete, download
     static let forDirectory: [NCUserPermission] = NCUserPermission.allCases
     static let forDirectoryE2EE: [NCUserPermission] = []
     static let forFile: [NCUserPermission] = [.reshare, .edit]
@@ -84,11 +99,13 @@ enum NCUserPermission: CaseIterable, NCPermission {
         case .edit: return NSLocalizedString("_share_can_change_", comment: "")
         case .create: return NSLocalizedString("_share_can_create_", comment: "")
         case .delete: return NSLocalizedString("_share_can_delete_", comment: "")
+        case .download: return NSLocalizedString("_share_can_download_", comment: "")
         }
     }
 }
 
 enum NCLinkPermission: NCPermission {
+
     func didChange(_ share: NCTableShareable, to newValue: Bool) {
         guard self != .allowEdit || newValue else {
             share.permissions = NCGlobal.shared.permissionReadShare
@@ -101,6 +118,10 @@ enum NCLinkPermission: NCPermission {
         permissionValue & parentPermission == permissionValue
     }
 
+    func hasDownload() -> Bool {
+        return false
+    }
+
     var permissionValue: Int {
         switch self {
         case .allowEdit:
@@ -225,7 +246,7 @@ struct NCShareConfig {
         let cellConfig = config(for: indexPath)
         let cell = cellConfig?.getCell(for: share)
         cell?.textLabel?.text = cellConfig?.title
-        if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasResharePermission(for: resharePermission) {
+        if let cellConfig = cellConfig as? NCPermission, !cellConfig.hasResharePermission(for: resharePermission), !cellConfig.hasDownload() {
             cell?.isUserInteractionEnabled = false
             cell?.textLabel?.isEnabled = false
         }

+ 5 - 0
iOSClient/Share/NCShare+Helper.swift

@@ -40,6 +40,8 @@ protocol NCTableShareable: AnyObject {
     var note: String { get set }
     var expirationDate: NSDate? { get set }
     var shareWithDisplayname: String { get set }
+
+    var attributes: String? { get set }
 }
 
 extension NCTableShareable {
@@ -62,6 +64,7 @@ extension NCTableShareable {
 }
 
 class NCTableShareOptions: NCTableShareable {
+
     var shareType: Int
     var permissions: Int
 
@@ -75,6 +78,8 @@ class NCTableShareOptions: NCTableShareable {
     var expirationDate: NSDate?
     var shareWithDisplayname: String = ""
 
+    var attributes: String?
+
     private init(shareType: Int, metadata: tableMetadata, password: String?) {
         if metadata.e2eEncrypted {
             self.permissions = NCGlobal.shared.permissionCreateShare

+ 2 - 2
iOSClient/Share/NCShareNetworking.swift

@@ -82,7 +82,7 @@ class NCShareNetworking: NSObject {
         NCActivityIndicator.shared.start(backgroundView: view)
         let filenamePath = CCUtility.returnFileNamePath(fromFileName: metadata.fileName, serverUrl: metadata.serverUrl, urlBase: metadata.urlBase, userId: metadata.userId, account: metadata.account)!
 
-        NextcloudKit.shared.createShare(path: filenamePath, shareType: option.shareType, shareWith: option.shareWith, password: option.password, permissions: option.permissions) { (account, share, data, error) in
+        NextcloudKit.shared.createShare(path: filenamePath, shareType: option.shareType, shareWith: option.shareWith, password: option.password, permissions: option.permissions, attributes: option.attributes) { (account, share, data, error) in
             NCActivityIndicator.shared.stop()
             if error == .success, let share = share {
                 option.idShare = share.idShare
@@ -113,7 +113,7 @@ class NCShareNetworking: NSObject {
 
     func updateShare(option: NCTableShareable) {
         NCActivityIndicator.shared.start(backgroundView: view)
-        NextcloudKit.shared.updateShare(idShare: option.idShare, password: option.password, expireDate: option.expDateString, permissions: option.permissions, note: option.note, label: option.label, hideDownload: option.hideDownload) { account, share, data, error in
+        NextcloudKit.shared.updateShare(idShare: option.idShare, password: option.password, expireDate: option.expDateString, permissions: option.permissions, note: option.note, label: option.label, hideDownload: option.hideDownload, attributes: option.attributes) { account, share, data, error in
             NCActivityIndicator.shared.stop()
             if error == .success, let share = share {
                 let home = NCUtilityFileSystem.shared.getHomeServer(urlBase: self.metadata.urlBase, userId: self.metadata.userId)

+ 22 - 27
iOSClient/Shares/NCShares.swift

@@ -52,40 +52,34 @@ class NCShares: NCCollectionViewCommon {
     override func reloadDataSource(forced: Bool = true) {
         super.reloadDataSource()
 
-        DispatchQueue.global().async {
-            let sharess = NCManageDatabase.shared.getTableShares(account: self.appDelegate.account)
-            var metadatas: [tableMetadata] = []
-            for share in sharess {
-                if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", self.appDelegate.account, share.serverUrl, share.fileName)) {
-                    if !(metadatas.contains { $0.ocId == metadata.ocId }) {
-                        metadatas.append(metadata)
-                    }
+        let sharess = NCManageDatabase.shared.getTableShares(account: self.appDelegate.account)
+        var metadatas: [tableMetadata] = []
+        for share in sharess {
+            if let metadata = NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", self.appDelegate.account, share.serverUrl, share.fileName)) {
+                if !(metadatas.contains { $0.ocId == metadata.ocId }) {
+                    metadatas.append(metadata)
                 }
             }
-
-            self.dataSource = NCDataSource(metadatas: metadatas,
-                                           account: self.appDelegate.account,
-                                           sort: self.layoutForView?.sort,
-                                           ascending: self.layoutForView?.ascending,
-                                           directoryOnTop: self.layoutForView?.directoryOnTop,
-                                           favoriteOnTop: true,
-                                           filterLivePhoto: true,
-                                           groupByField: self.groupByField,
-                                           providers: self.providers,
-                                           searchResults: self.searchResults)
-
-            DispatchQueue.main.async {
-                self.refreshControl.endRefreshing()
-                self.collectionView.reloadData()
-            }
         }
+        self.dataSource = NCDataSource(metadatas: metadatas,
+                                       account: self.appDelegate.account,
+                                       sort: self.layoutForView?.sort,
+                                       ascending: self.layoutForView?.ascending,
+                                       directoryOnTop: self.layoutForView?.directoryOnTop,
+                                       favoriteOnTop: true,
+                                       filterLivePhoto: true,
+                                       groupByField: self.groupByField,
+                                       providers: self.providers,
+                                       searchResults: self.searchResults)
+
+
+        self.refreshControl.endRefreshing()
+        self.collectionView.reloadData()
     }
 
     override func reloadDataSourceNetwork(forced: Bool = false) {
         super.reloadDataSourceNetwork(forced: forced)
 
-        if UIApplication.shared.applicationState != .active { return }
-
         isReloadDataSourceNetworkInProgress = true
         collectionView?.reloadData()
 
@@ -95,10 +89,11 @@ class NCShares: NCCollectionViewCommon {
             self.isReloadDataSourceNetworkInProgress = false
 
             if error == .success {
-                NCManageDatabase.shared.deleteTableShare(account: account)
                 if let shares = shares, !shares.isEmpty {
                     let home = NCUtilityFileSystem.shared.getHomeServer(urlBase: self.appDelegate.urlBase, userId: self.appDelegate.userId)
                     NCManageDatabase.shared.addShare(account: self.appDelegate.account, home: home, shares: shares)
+                } else {
+                    NCManageDatabase.shared.deleteTableShare(account: account)
                 }
                 self.reloadDataSource()
 

BIN
iOSClient/Supporting Files/af.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/an.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ar.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ast.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/az.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/be.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bg_BG.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bn_BD.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/br.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/bs.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ca.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/cy_GB.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/da.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/de.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/el.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/en-GB.lproj/Localizable.strings


+ 3 - 1
iOSClient/Supporting Files/en.lproj/Localizable.strings

@@ -112,7 +112,8 @@
 "_force_start_"             = "Force the start";
 "_purchase_"                = "Purchase";
 "_account_not_available_"   = "The account %@ of %@ does not exist, please add it to be able to read the file %@";
-"_account_not_exists_"      = "The account %@ of %@ does not exist";
+"_the_account_"             = "The account";
+"_does_not_exist_"          = "does not exist";
 "_error_parameter_schema_"  = "Wrong parameters, impossible to continue";
 "_comments_"                = "Comments";
 "_sharing_"                 = "Sharing";
@@ -648,6 +649,7 @@
 "_share_can_create_"            = "Allow creating";
 "_share_can_change_"            = "Allow editing";
 "_share_can_delete_"            = "Allow deleting";
+"_share_can_download_"          = "Allow download";
 "_share_unshare_"               = "Unshare";
 "_share_internal_link_"         = "Internal link";
 "_share_internal_link_des_"     = "Only works for users with access to this folder";

BIN
iOSClient/Supporting Files/eo.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-419.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-AR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CL.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-CR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-DO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-EC.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-GT.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-HN.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-MX.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-NI.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PA.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PE.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PR.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-PY.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-SV.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es-UY.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/es.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/et_EE.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/eu.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fa.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fo.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/fr.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/gd.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/gl.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/he.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hi_IN.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hr.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hsb.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hu.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/hy.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ia.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/id.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ig.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/is.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/it.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ja-JP.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ka-GE.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ka.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/kab.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/km.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/kn.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ko.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/la.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lb.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lo.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lt_LT.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/lv.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/mk.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/mn.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/mr.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ms_MY.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/my.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/nb-NO.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/ne.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/nl.lproj/Localizable.strings


BIN
iOSClient/Supporting Files/nn_NO.lproj/Localizable.strings


Some files were not shown because too many files changed in this diff