diff --git a/File Provider Extension/FileProviderData.swift b/File Provider Extension/FileProviderData.swift index bcff143cf2..d0568579e4 100644 --- a/File Provider Extension/FileProviderData.swift +++ b/File Provider Extension/FileProviderData.swift @@ -91,7 +91,7 @@ class fileProviderData: NSObject { homeServerUrl = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) NCManageDatabase.shared.setCapabilities(account: account) - NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, groupIdentifier: NCBrandOptions.shared.capabilitiesGroup, delegate: NCNetworking.shared) + NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared) NCNetworking.shared.delegate = providerExtension as? NCNetworkingDelegate return tableAccount.init(value: activeAccount) @@ -114,7 +114,7 @@ class fileProviderData: NSObject { NCManageDatabase.shared.setCapabilities(account: account) - NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, groupIdentifier: NCBrandOptions.shared.capabilitiesGroup, delegate: NCNetworking.shared) + NextcloudKit.shared.setup(account: activeAccount.account, user: activeAccount.user, userId: activeAccount.userId, password: NCKeychain().getPassword(account: activeAccount.account), urlBase: activeAccount.urlBase, userAgent: userAgent, nextcloudVersion: NCGlobal.shared.capabilityServerVersionMajor, delegate: NCNetworking.shared) NCNetworking.shared.delegate = providerExtension as? NCNetworkingDelegate return tableAccount.init(value: activeAccount) diff --git a/File Provider Extension/FileProviderExtension+Actions.swift b/File Provider Extension/FileProviderExtension+Actions.swift index 198f5ff29a..1f5f1811a3 100644 --- a/File Provider Extension/FileProviderExtension+Actions.swift +++ b/File Provider Extension/FileProviderExtension+Actions.swift @@ -54,7 +54,7 @@ extension FileProviderExtension { } } } else { - completionHandler(nil, NSFileProviderError(.serverUnreachable)) + completionHandler(nil, NSFileProviderError(.filenameCollision)) } } } @@ -70,7 +70,7 @@ extension FileProviderExtension { let fileName = metadata.fileName NextcloudKit.shared.deleteFileOrFolder(serverUrlFileName: serverUrlFileName, account: metadata.account) { account, error in - if error == .success { // || error == kOCErrorServerPathNotFound { + if error == .success { let fileNamePath = self.utilityFileSystem.getDirectoryProviderStorageOcId(itemIdentifier.rawValue) do { @@ -122,6 +122,8 @@ extension FileProviderExtension { let item = FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier) completionHandler(item, nil) + } else if error.errorCode == NCGlobal.shared.errorBadRequest { + completionHandler(nil, NSFileProviderError(.noSuchItem, userInfo: [NSLocalizedDescriptionKey: error.errorDescription, NSLocalizedFailureReasonErrorKey: ""])) } else { completionHandler(nil, NSFileProviderError(.serverUnreachable)) } @@ -161,6 +163,8 @@ extension FileProviderExtension { } let item = FileProviderItem(metadata: tableMetadata.init(value: metadata), parentItemIdentifier: parentItemIdentifier) completionHandler(item, nil) + } else if error.errorCode == NCGlobal.shared.errorBadRequest { + completionHandler(nil, NSFileProviderError(.noSuchItem, userInfo: [NSLocalizedDescriptionKey: error.errorDescription, NSLocalizedFailureReasonErrorKey: ""])) } else { completionHandler(nil, NSFileProviderError(.serverUnreachable)) } diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 7651daabfd..603a992b89 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -197,8 +197,6 @@ F70CAE3A1F8CF31A008125FD /* NCEndToEndEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = F70CAE391F8CF31A008125FD /* NCEndToEndEncryption.m */; }; F70CEF5623E9C7E50007035B /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70CEF5523E9C7E50007035B /* UIColor+Extension.swift */; }; F70D7C3725FFBF82002B9E34 /* NCCollectionViewCommon.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D7C3525FFBF81002B9E34 /* NCCollectionViewCommon.swift */; }; - F70D87CF25EE6E58008CBBBD /* NCRenameFile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F70D87CD25EE6E58008CBBBD /* NCRenameFile.storyboard */; }; - F70D87D025EE6E58008CBBBD /* NCRenameFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D87CE25EE6E58008CBBBD /* NCRenameFile.swift */; }; F70D8D8124A4A9BF000A5756 /* NCNetworkingProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */; }; F710D1F52405770F00A6033D /* NCViewerPDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = F710D1F42405770F00A6033D /* NCViewerPDF.swift */; }; F710D2022405826100A6033D /* NCViewer+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F710D2012405826100A6033D /* NCViewer+Menu.swift */; }; @@ -712,8 +710,6 @@ F79B646226CA661600838ACA /* UIControl+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79B645F26CA661600838ACA /* UIControl+Extension.swift */; }; F79B646326CA661600838ACA /* UIControl+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79B645F26CA661600838ACA /* UIControl+Extension.swift */; }; F79B869B265E19D40085C0E0 /* NSMutableAttributedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79B869A265E19D40085C0E0 /* NSMutableAttributedString+Extension.swift */; }; - F79EC77F26316193004E59D6 /* NCRenameFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F70D87CE25EE6E58008CBBBD /* NCRenameFile.swift */; }; - F79EC784263161BA004E59D6 /* NCRenameFile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F70D87CD25EE6E58008CBBBD /* NCRenameFile.storyboard */; }; F79EC78926316AC4004E59D6 /* NCPopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F702F30725EE5D47008F8E80 /* NCPopupViewController.swift */; }; F79EDAA326B004980007D134 /* NCPlayerToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79EDA9F26B004980007D134 /* NCPlayerToolBar.swift */; }; F79EDAA526B004980007D134 /* NCPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F79EDAA126B004980007D134 /* NCPlayer.swift */; }; @@ -1217,8 +1213,6 @@ F70CAE391F8CF31A008125FD /* NCEndToEndEncryption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NCEndToEndEncryption.m; sourceTree = ""; }; F70CEF5523E9C7E50007035B /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; F70D7C3525FFBF81002B9E34 /* NCCollectionViewCommon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCCollectionViewCommon.swift; sourceTree = ""; }; - F70D87CD25EE6E58008CBBBD /* NCRenameFile.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = NCRenameFile.storyboard; sourceTree = ""; }; - F70D87CE25EE6E58008CBBBD /* NCRenameFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCRenameFile.swift; sourceTree = ""; }; F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingProcess.swift; sourceTree = ""; }; F70F2BA4225F2D8900EBB73E /* ZIPFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZIPFoundation.framework; path = Carthage/Build/iOS/ZIPFoundation.framework; sourceTree = ""; }; F70F96AF2874394B006C8379 /* Nextcloud-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Nextcloud-Bridging-Header.h"; sourceTree = ""; }; @@ -2082,15 +2076,6 @@ path = Color; sourceTree = ""; }; - F70D87CC25EE6E58008CBBBD /* Rename file */ = { - isa = PBXGroup; - children = ( - F70D87CD25EE6E58008CBBBD /* NCRenameFile.storyboard */, - F70D87CE25EE6E58008CBBBD /* NCRenameFile.swift */, - ); - path = "Rename file"; - sourceTree = ""; - }; F713418B2597513800768D21 /* PushNotification */ = { isa = PBXGroup; children = ( @@ -2980,7 +2965,6 @@ F7381ED9218218A4000B1560 /* Offline */, F713418B2597513800768D21 /* PushNotification */, F765F72E25237E3F00391DBE /* Recent */, - F70D87CC25EE6E58008CBBBD /* Rename file */, F7CADB3D23CCDDA1000EEC78 /* RichWorkspace */, F76882042C0DD1E7001CF441 /* Settings */, F758B41E212C516300515F55 /* Scan document */, @@ -3668,7 +3652,6 @@ F7145A231D12E3B700CAFEEC /* Localizable.strings in Resources */, F746EC51273906C40052598D /* NCViewCertificateDetails.storyboard in Resources */, F76C26A62850D3A500E42BDF /* Images.xcassets in Resources */, - F79EC784263161BA004E59D6 /* NCRenameFile.storyboard in Resources */, F714805E262ED52900693E51 /* NCSectionFooter.xib in Resources */, F700222D1EC479840080073F /* Custom.xcassets in Resources */, ); @@ -3714,7 +3697,6 @@ F702F2F125EE5CDB008F8E80 /* NCLogin.storyboard in Resources */, F723985C253C95CE00257F49 /* NCViewerRichdocument.storyboard in Resources */, F758B45A212C564000515F55 /* NCScan.storyboard in Resources */, - F70D87CF25EE6E58008CBBBD /* NCRenameFile.storyboard in Resources */, F765F73225237E3F00391DBE /* NCRecent.storyboard in Resources */, F78F74342163757000C2ADAD /* NCTrash.storyboard in Resources */, F702F30225EE5D2C008F8E80 /* english.txt in Resources */, @@ -4046,7 +4028,6 @@ F76D364728A4F8BF00214537 /* NCActivityIndicator.swift in Sources */, F73EF7CA2B0225610087E6E9 /* NCManageDatabase+PhotoLibrary.swift in Sources */, F749B654297B0F2400087535 /* NCManageDatabase+Avatar.swift in Sources */, - F79EC77F26316193004E59D6 /* NCRenameFile.swift in Sources */, AF22B208277B4E4C00DAB0CC /* NCCreateFormUploadConflictCell.swift in Sources */, F74C86382AEFBE64009A1D4A /* NCImageCache.swift in Sources */, F73EF7C22B02250B0087E6E9 /* NCManageDatabase+GPS.swift in Sources */, @@ -4234,7 +4215,6 @@ F702F30825EE5D47008F8E80 /* NCPopupViewController.swift in Sources */, F733598125C1C188002ABA72 /* NCAskAuthorization.swift in Sources */, 370D26AF248A3D7A00121797 /* NCCellProtocol.swift in Sources */, - F70D87D025EE6E58008CBBBD /* NCRenameFile.swift in Sources */, F768822C2C0DD1E7001CF441 /* NCKeychain.swift in Sources */, F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */, F73EF7D72B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */, @@ -5778,8 +5758,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - kind = exactVersion; - version = 4.0.6; + branch = "filename-validator"; + kind = branch; }; }; F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = { diff --git a/Share/NCShareExtension+DataSource.swift b/Share/NCShareExtension+DataSource.swift index 2d10a75bf8..e96e0aaa5b 100644 --- a/Share/NCShareExtension+DataSource.swift +++ b/Share/NCShareExtension+DataSource.swift @@ -35,6 +35,11 @@ extension NCShareExtension: UICollectionViewDelegate { showAlert(title: "_info_", description: "_e2e_goto_settings_for_enable_") } + if let fileNameError = FileNameValidator.shared.checkFileName(metadata.fileNameView) { + present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + return + } + self.serverUrl = serverUrl reloadDatasource(withLoadFolder: true) setNavigationBar(navigationTitle: metadata.fileNameView) @@ -84,8 +89,6 @@ extension NCShareExtension: UICollectionViewDataSource { return UICollectionViewCell() } - cell.listCellDelegate = self - cell.fileObjectId = metadata.ocId cell.indexPath = indexPath cell.fileUser = metadata.ownerId diff --git a/Share/NCShareExtension+Files.swift b/Share/NCShareExtension+Files.swift index 3d8565c827..1da8b52ef6 100644 --- a/Share/NCShareExtension+Files.swift +++ b/Share/NCShareExtension+Files.swift @@ -23,6 +23,7 @@ import Foundation import UniformTypeIdentifiers +import NextcloudKit extension NCShareExtension { @objc func reloadDatasource(withLoadFolder: Bool) { diff --git a/Share/NCShareExtension+NCDelegate.swift b/Share/NCShareExtension+NCDelegate.swift index d13e00f681..a3a1e1f76e 100644 --- a/Share/NCShareExtension+NCDelegate.swift +++ b/Share/NCShareExtension+NCDelegate.swift @@ -93,10 +93,30 @@ extension NCShareExtension: NCAccountRequestDelegate { reloadDatasource(withLoadFolder: true) setNavigationBar(navigationTitle: NCBrandOptions.shared.brand) + + FileNameValidator.shared.setup( + forbiddenFileNames: NCGlobal.shared.capabilityForbiddenFileNames, + forbiddenFileNameBasenames: NCGlobal.shared.capabilityForbiddenFileNameBasenames, + forbiddenFileNameCharacters: NCGlobal.shared.capabilityForbiddenFileNameCharacters, + forbiddenFileNameExtensions: NCGlobal.shared.capabilityForbiddenFileNameExtensions + ) + } +} + +extension NCShareExtension: NCCreateFormUploadConflictDelegate { + func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) { + guard let metadatas = metadatas else { + uploadStarted = false + uploadMetadata.removeAll() + return + } + + self.uploadMetadata.append(contentsOf: metadatas) + self.upload() } } -extension NCShareExtension: NCShareCellDelegate, NCRenameFileDelegate, NCListCellDelegate { +extension NCShareExtension: NCShareCellDelegate { func removeFile(named fileName: String) { guard let index = self.filesName.firstIndex(of: fileName) else { return showAlert(title: "_file_not_found_", description: fileName) @@ -110,54 +130,17 @@ extension NCShareExtension: NCShareCellDelegate, NCRenameFileDelegate, NCListCel } func renameFile(named fileName: String) { - guard let vcRename = UIStoryboard(name: "NCRenameFile", bundle: nil).instantiateInitialViewController() as? NCRenameFile else { return } - - let resultInternalType = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: fileName, mimeType: "", directory: false) - vcRename.delegate = self - vcRename.fileName = fileName - vcRename.indexPath = IndexPath() - if let previewImage = UIImage.downsample(imageAt: URL(fileURLWithPath: NSTemporaryDirectory() + fileName), to: CGSize(width: 140, height: 140)) { - vcRename.imagePreview = previewImage - } else { - vcRename.imagePreview = UIImage(named: resultInternalType.iconName) ?? NCImageCache.images.file + let alert = UIAlertController.renameFile(fileName: fileName) { [self] newFileName in + guard let fileIx = self.filesName.firstIndex(of: fileName), + !self.filesName.contains(newFileName), + utilityFileSystem.moveFile(atPath: (NSTemporaryDirectory() + fileName), toPath: (NSTemporaryDirectory() + newFileName)) else { + return showAlert(title: "_single_file_conflict_title_", description: "'\(fileName)' -> '\(newFileName)'") + } + + filesName[fileIx] = newFileName + tableView.reloadData() } - let popup = NCPopupViewController(contentController: vcRename, popupWidth: vcRename.width, popupHeight: vcRename.height) - - self.present(popup, animated: true) - } - - func rename(fileName: String, fileNameNew: String) { - guard fileName != fileNameNew else { return } - guard let fileIx = self.filesName.firstIndex(of: fileName), - !self.filesName.contains(fileNameNew), - utilityFileSystem.moveFile(atPath: (NSTemporaryDirectory() + fileName), toPath: (NSTemporaryDirectory() + fileNameNew)) else { - return showAlert(title: "_single_file_conflict_title_", description: "'\(fileName)' -> '\(fileNameNew)'") - } - - filesName[fileIx] = fileNameNew - tableView.reloadData() - } - - func tapShareListItem(with objectId: String, indexPath: IndexPath, sender: Any) { - } - - func tapMoreListItem(with objectId: String, namedButtonMore: String, image: UIImage?, indexPath: IndexPath, sender: Any) { - } - - func longPressListItem(with objectId: String, indexPath: IndexPath, gestureRecognizer: UILongPressGestureRecognizer) { - } -} - -extension NCShareExtension: NCCreateFormUploadConflictDelegate { - func dismissCreateFormUploadConflict(metadatas: [tableMetadata]?) { - guard let metadatas = metadatas else { - uploadStarted = false - uploadMetadata.removeAll() - return - } - - self.uploadMetadata.append(contentsOf: metadatas) - self.upload() + present(alert, animated: true) } } diff --git a/Share/NCShareExtension.swift b/Share/NCShareExtension.swift index d7c91c4e8f..47fa2bd13f 100644 --- a/Share/NCShareExtension.swift +++ b/Share/NCShareExtension.swift @@ -298,6 +298,12 @@ extension NCShareExtension { var conflicts: [tableMetadata] = [] for fileName in filesName { + if let fileNameError = FileNameValidator.shared.checkFileName(fileName) { + present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + + continue + } + let ocId = NSUUID().uuidString let toPath = utilityFileSystem.getDirectoryProviderStorageOcId(ocId, fileNameView: fileName) guard utilityFileSystem.copyFile(atPath: (NSTemporaryDirectory() + fileName), toPath: toPath) else { continue } @@ -335,6 +341,7 @@ extension NCShareExtension { guard uploadStarted else { return } guard uploadMetadata.count > counterUploaded else { return DispatchQueue.main.async { self.finishedUploading() } } let metadata = uploadMetadata[counterUploaded] + let results = NextcloudKit.shared.nkCommonInstance.getInternalType(fileName: metadata.fileNameView, mimeType: metadata.contentType, directory: false) metadata.contentType = results.mimeType metadata.iconName = results.iconName diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index c80bae2eb4..fefc6466f7 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -109,7 +109,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD userId = activeAccount.userId password = NCKeychain().getPassword(account: account) - NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: urlBase, groupIdentifier: NCBrandOptions.shared.capabilitiesGroup) + NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: urlBase) NCManageDatabase.shared.setCapabilities(account: account) NCBrandColor.shared.settingThemingColor(account: activeAccount.account) @@ -148,6 +148,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.handleProcessingTask(task) } + FileNameValidator.shared.setup( + forbiddenFileNames: NCGlobal.shared.capabilityForbiddenFileNames, + forbiddenFileNameBasenames: NCGlobal.shared.capabilityForbiddenFileNameBasenames, + forbiddenFileNameCharacters: NCGlobal.shared.capabilityForbiddenFileNameCharacters, + forbiddenFileNameExtensions: NCGlobal.shared.capabilityForbiddenFileNameExtensions + ) + return true } @@ -462,7 +469,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD if urlBase.last == "/" { urlBase = String(urlBase.dropLast()) } let account: String = "\(user) \(urlBase)" - NextcloudKit.shared.setup(account: account, user: user, userId: user, password: password, urlBase: urlBase, groupIdentifier: NCBrandOptions.shared.capabilitiesGroup) + NextcloudKit.shared.setup(account: account, user: user, userId: user, password: password, urlBase: urlBase) NextcloudKit.shared.getUserProfile(account: account) { account, userProfile, _, error in if error == .success, let userProfile { NCManageDatabase.shared.deleteAccount(account) @@ -472,7 +479,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD completion(error) } } else { - NextcloudKit.shared.setup(account: self.account, user: self.user, userId: self.userId, password: self.password, urlBase: self.urlBase, groupIdentifier: NCBrandOptions.shared.capabilitiesGroup) + NextcloudKit.shared.setup(account: self.account, user: self.user, userId: self.userId, password: self.password, urlBase: self.urlBase) let alertController = UIAlertController(title: NSLocalizedString("_error_", comment: ""), message: error.errorDescription, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default, handler: { _ in })) UIApplication.shared.firstWindow?.rootViewController?.present(alertController, animated: true) @@ -509,7 +516,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.userId = tableAccount.userId self.password = NCKeychain().getPassword(account: tableAccount.account) - NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: urlBase, groupIdentifier: NCBrandOptions.shared.capabilitiesGroup) + NextcloudKit.shared.setup(account: account, user: user, userId: userId, password: password, urlBase: urlBase) NCManageDatabase.shared.setCapabilities(account: account) if let userProfile { @@ -523,6 +530,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Initialize Auto upload with \(items) uploads") } + FileNameValidator.shared.setup( + forbiddenFileNames: NCGlobal.shared.capabilityForbiddenFileNames, + forbiddenFileNameBasenames: NCGlobal.shared.capabilityForbiddenFileNameBasenames, + forbiddenFileNameCharacters: NCGlobal.shared.capabilityForbiddenFileNameCharacters, + forbiddenFileNameExtensions: NCGlobal.shared.capabilityForbiddenFileNameExtensions + ) + NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterChangeUser) completion() } diff --git a/iOSClient/Data/NCManageDatabase+Capabilities.swift b/iOSClient/Data/NCManageDatabase+Capabilities.swift index 73820d709a..1a67389ee5 100644 --- a/iOSClient/Data/NCManageDatabase+Capabilities.swift +++ b/iOSClient/Data/NCManageDatabase+Capabilities.swift @@ -229,11 +229,19 @@ extension NCManageDatabase { let bigfilechunking: Bool? let versiondeletion: Bool? let versionlabeling: Bool? + let forbiddenFileNames: [String]? + let forbiddenFileNameBasenames: [String]? + let forbiddenFileNameCharacters: [String]? + let forbiddenFileNameExtensions: [String]? enum CodingKeys: String, CodingKey { case undelete, locking, comments, versioning, directEditing, bigfilechunking case versiondeletion = "version_deletion" case versionlabeling = "version_labeling" + case forbiddenFileNames = "forbidden_filenames" + case forbiddenFileNameBasenames = "forbidden_filename_basenames" + case forbiddenFileNameCharacters = "forbidden_filename_characters" + case forbiddenFileNameExtensions = "forbidden_filename_extensions" } struct DirectEditing: Codable { @@ -360,6 +368,11 @@ extension NCManageDatabase { } global.capabilityGroupfoldersEnabled = data.capabilities.groupfolders?.hasGroupFolders ?? false global.capabilitySecurityGuardDiagnostics = data.capabilities.securityguard?.diagnostics ?? false + + global.capabilityForbiddenFileNames = data.capabilities.files?.forbiddenFileNames ?? [] + global.capabilityForbiddenFileNameBasenames = data.capabilities.files?.forbiddenFileNameBasenames ?? [] + global.capabilityForbiddenFileNameCharacters = data.capabilities.files?.forbiddenFileNameCharacters ?? [] + global.capabilityForbiddenFileNameExtensions = data.capabilities.files?.forbiddenFileNameExtensions ?? [] } catch let error as NSError { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") return diff --git a/iOSClient/DeepLink/NCDeepLinkHandler.swift b/iOSClient/DeepLink/NCDeepLinkHandler.swift index d45533d0dd..cca6971d63 100644 --- a/iOSClient/DeepLink/NCDeepLinkHandler.swift +++ b/iOSClient/DeepLink/NCDeepLinkHandler.swift @@ -22,6 +22,7 @@ import Foundation import UIKit import SwiftUI +import NextcloudKit enum DeepLink: String { case openFiles // nextcloud://openFiles @@ -109,6 +110,16 @@ class NCDeepLinkHandler { guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } controller.selectedIndex = ControllerConstants.filesIndex DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + let serverUrl = controller.currentServerUrl() + let fileFolderPath = NCUtilityFileSystem().getFileNamePath("", serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + let fileFolderName = (serverUrl as NSString).lastPathComponent + + if !FileNameValidator.shared.checkFolderPath(folderPath: fileFolderPath) { + controller.present(UIAlertController.warning(message: "\(String(format: NSLocalizedString("_file_name_validator_error_reserved_name_", comment: ""), fileFolderName)) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + + return + } + appDelegate.toggleMenu(controller: controller) } } diff --git a/iOSClient/Extensions/UIAlertController+Extension.swift b/iOSClient/Extensions/UIAlertController+Extension.swift index 0eab5ef65f..97357bb94b 100644 --- a/iOSClient/Extensions/UIAlertController+Extension.swift +++ b/iOSClient/Extensions/UIAlertController+Extension.swift @@ -75,7 +75,10 @@ extension UIAlertController { queue: .main) { _ in guard let text = alertController.textFields?.first?.text else { return } let folderName = text.trimmingCharacters(in: .whitespaces) - okAction.isEnabled = !folderName.isEmpty && folderName != "." && folderName != ".." + + let textCheck = FileNameValidator.shared.checkFileName(folderName) + okAction.isEnabled = textCheck?.error == nil && !folderName.isEmpty + alertController.message = textCheck?.error.localizedDescription } alertController.addAction(cancelAction) @@ -149,4 +152,123 @@ extension UIAlertController { }) return alertController } + + static func renameFile(fileName: String, completion: @escaping (_ newFileName: String) -> Void) -> UIAlertController { + let alertController = UIAlertController(title: NSLocalizedString("_rename_", comment: ""), message: nil, preferredStyle: .alert) + + let okAction = UIAlertAction(title: NSLocalizedString("_save_", comment: ""), style: .default, handler: { _ in + guard let newFileName = alertController.textFields?.first?.text else { return } + + completion(newFileName) + }) + + // text field is initially empty, no action + okAction.isEnabled = false + let cancelAction = UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) + + alertController.addTextField { textField in + textField.text = fileName + textField.autocapitalizationType = .words + } + + // only allow saving if folder name exists + NotificationCenter.default.addObserver( + forName: UITextField.textDidBeginEditingNotification, + object: alertController.textFields?.first, + queue: .main) { _ in + guard let textField = alertController.textFields?.first else { return } + + if let start = textField.position(from: textField.beginningOfDocument, offset: 0), + let end = textField.position(from: start, offset: textField.text?.withRemovedFileExtension.count ?? 0) { + textField.selectedTextRange = textField.textRange(from: start, to: end) + } + } + + NotificationCenter.default.addObserver( + forName: UITextField.textDidChangeNotification, + object: alertController.textFields?.first, + queue: .main) { _ in + guard let text = alertController.textFields?.first?.text else { return } + + let textCheck = FileNameValidator.shared.checkFileName(text) + okAction.isEnabled = textCheck?.error == nil && !text.isEmpty + alertController.message = textCheck?.error.localizedDescription + } + + alertController.addAction(cancelAction) + alertController.addAction(okAction) + return alertController + } + + static func renameFile(metadata: tableMetadata, indexPath: IndexPath) -> UIAlertController { + let alertController = UIAlertController(title: NSLocalizedString("_rename_", comment: ""), message: nil, preferredStyle: .alert) + + let okAction = UIAlertAction(title: NSLocalizedString("_save_", comment: ""), style: .default, handler: { _ in + guard let newFileName = alertController.textFields?.first?.text else { return } + + // verify if already exists + if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, newFileName)) != nil { + NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) + return + } + + NCActivityIndicator.shared.start() + + NCNetworking.shared.renameMetadata(metadata, fileNameNew: newFileName, indexPath: indexPath) { error in + + NCActivityIndicator.shared.stop() + + if error != .success { + NCContentPresenter().showError(error: error) + } + } + }) + + // text field is initially empty, no action + okAction.isEnabled = false + let cancelAction = UIAlertAction(title: NSLocalizedString("_cancel_", comment: ""), style: .cancel) + + alertController.addTextField { textField in + textField.text = metadata.fileName + textField.autocapitalizationType = .words + } + + // only allow saving if folder name exists + NotificationCenter.default.addObserver( + forName: UITextField.textDidBeginEditingNotification, + object: alertController.textFields?.first, + queue: .main) { _ in + guard let textField = alertController.textFields?.first else { return } + + if let start = textField.position(from: textField.beginningOfDocument, offset: 0), + let end = textField.position(from: start, offset: textField.text?.withRemovedFileExtension.count ?? 0) { + textField.selectedTextRange = textField.textRange(from: start, to: end) + } + } + + NotificationCenter.default.addObserver( + forName: UITextField.textDidChangeNotification, + object: alertController.textFields?.first, + queue: .main) { _ in + guard let text = alertController.textFields?.first?.text else { return } + + let textCheck = FileNameValidator.shared.checkFileName(text) + okAction.isEnabled = textCheck?.error == nil && !text.isEmpty + alertController.message = textCheck?.error.localizedDescription + } + + alertController.addAction(cancelAction) + alertController.addAction(okAction) + return alertController + } + + static func warning(title: String? = nil, message: String? = nil) -> UIAlertController { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + + let okAction = UIAlertAction(title: NSLocalizedString("_ok_", comment: ""), style: .default) + + alertController.addAction(okAction) + + return alertController + } } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift index 56fc6d6eed..39e0dcebcc 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon+SelectTabBar.swift @@ -87,6 +87,7 @@ extension NCCollectionViewCommon: NCCollectionViewCommonSelectTabBarDelegate { func move() { let metadatas = getSelectedMetadatas() + NCActionCenter.shared.openSelectView(items: metadatas, controller: self.tabBarController as? NCMainTabBarController) setEditMode(false) } diff --git a/iOSClient/Main/Create cloud/Upload Assets/NCUploadAssetsView.swift b/iOSClient/Main/Create cloud/Upload Assets/NCUploadAssetsView.swift index 12079dcefa..3f8bd26a23 100644 --- a/iOSClient/Main/Create cloud/Upload Assets/NCUploadAssetsView.swift +++ b/iOSClient/Main/Create cloud/Upload Assets/NCUploadAssetsView.swift @@ -7,13 +7,15 @@ // import SwiftUI +import NextcloudKit struct NCUploadAssetsView: View { @ObservedObject var model: NCUploadAssetsModel @State private var showSelect = false @State private var showUploadConflict = false @State private var showQuickLook = false - @State private var shorRenameAlert = false + @State private var showRenameAlert = false + @State private var renameError = "" @State private var renameFileName: String = "" @State private var renameIndex: Int = 0 @State private var index: Int = 0 @@ -39,7 +41,7 @@ struct NCUploadAssetsView: View { Button(action: { renameFileName = model.previewStore[index].fileName renameIndex = index - shorRenameAlert = true + showRenameAlert = true }) { Label(NSLocalizedString("_rename_", comment: ""), systemImage: "pencil") } @@ -84,21 +86,37 @@ struct NCUploadAssetsView: View { } } label: { ImageAsset(model: model, index: index) - .alert(NSLocalizedString("_rename_file_", comment: ""), isPresented: $shorRenameAlert) { - TextField(NSLocalizedString("_enter_filename_", comment: ""), text: $renameFileName) + .alert(NSLocalizedString("_rename_", comment: ""), isPresented: $showRenameAlert) { + TextField("", text: $renameFileName) .autocapitalization(.none) .autocorrectionDisabled() + Button(NSLocalizedString("_rename_", comment: ""), action: { - model.previewStore[renameIndex].fileName = renameFileName.trimmingCharacters(in: .whitespacesAndNewlines) + if !renameError.isEmpty { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + showRenameAlert = true + } + } else { + model.previewStore[renameIndex].fileName = renameFileName.trimmingCharacters(in: .whitespacesAndNewlines) + } }) + Button(NSLocalizedString("_cancel_", comment: ""), role: .cancel, action: {}) + } message: { + Text(renameError) } } + .onChange(of: renameFileName) { newValue in + if let error = FileNameValidator.shared.checkFileName(newValue) { + renameError = error.errorDescription + } else { + renameError = "" + } + } } } } } - // .redacted(reason: uploadAssets.previewStore.isEmpty ? .placeholder : []) Section { Toggle(isOn: $model.useAutoUploadFolder, label: { diff --git a/iOSClient/Main/NCActionCenter.swift b/iOSClient/Main/NCActionCenter.swift index f2d8e46cb8..1772f8d4b5 100644 --- a/iOSClient/Main/NCActionCenter.swift +++ b/iOSClient/Main/NCActionCenter.swift @@ -575,6 +575,12 @@ class NCActionCenter: NSObject, UIDocumentInteractionControllerDelegate, NCSelec var copyItems: [tableMetadata] = [] for item in items { + if let fileNameError = FileNameValidator.shared.checkFileName(item.fileNameView) { + controller?.present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + + return + } + copyItems.append(item) } diff --git a/iOSClient/Main/NCMainTabBar.swift b/iOSClient/Main/NCMainTabBar.swift index 5aa20d18d6..b1449cf3a4 100644 --- a/iOSClient/Main/NCMainTabBar.swift +++ b/iOSClient/Main/NCMainTabBar.swift @@ -180,7 +180,7 @@ class NCMainTabBar: UITabBar { centerButton.layer.shadowOffset = CGSize(width: 0, height: 0) centerButton.layer.shadowRadius = 3.0 centerButton.layer.shadowOpacity = 0.5 - centerButton.action(for: .touchUpInside) { _ in + centerButton.action(for: .touchUpInside) { [self] _ in if let controller = self.window?.rootViewController as? NCMainTabBarController { let serverUrl = controller.currentServerUrl() @@ -191,6 +191,16 @@ class NCMainTabBar: UITabBar { return } } + + let fileFolderPath = NCUtilityFileSystem().getFileNamePath("", serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) + let fileFolderName = (serverUrl as NSString).lastPathComponent + + if !FileNameValidator.shared.checkFolderPath(folderPath: fileFolderPath) { + controller.present(UIAlertController.warning(message: "\(String(format: NSLocalizedString("_file_name_validator_error_reserved_name_", comment: ""), fileFolderName)) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + + return + } + self.appDelegate.toggleMenu(controller: controller) } } diff --git a/iOSClient/Main/NCPickerViewController.swift b/iOSClient/Main/NCPickerViewController.swift index 19b09e5f8b..97afaaf4a8 100644 --- a/iOSClient/Main/NCPickerViewController.swift +++ b/iOSClient/Main/NCPickerViewController.swift @@ -142,8 +142,13 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate { if metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue { metadata.classFile = NKCommon.TypeClassFile.video.rawValue } - NCManageDatabase.shared.addMetadata(metadata) - NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + + if let fileNameError = FileNameValidator.shared.checkFileName(metadata.fileNameView) { + mainTabBarController.present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + } else { + NCManageDatabase.shared.addMetadata(metadata) + NCViewer().view(viewController: viewController, metadata: metadata, metadatas: [metadata], imageIcon: nil) + } } else { let serverUrl = mainTabBarController.currentServerUrl() @@ -161,6 +166,11 @@ class NCDocumentPickerViewController: NSObject, UIDocumentPickerDelegate { 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: "") + if let fileNameError = FileNameValidator.shared.checkFileName(metadataForUpload.fileNameView) { + mainTabBarController.present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + continue + } + metadataForUpload.session = NCNetworking.shared.sessionUploadBackground metadataForUpload.sessionSelector = NCGlobal.shared.selectorUploadFile metadataForUpload.size = utilityFileSystem.getFileSize(filePath: toPath) diff --git a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift index 224a4e35a3..8fb5bf9c13 100644 --- a/iOSClient/Menu/NCCollectionViewCommon+Menu.swift +++ b/iOSClient/Menu/NCCollectionViewCommon+Menu.swift @@ -281,17 +281,7 @@ extension NCCollectionViewCommon { icon: utility.loadImage(named: "text.cursor", colors: [NCBrandColor.shared.iconImageColor]), order: 120, action: { _ in - - if let vcRename = UIStoryboard(name: "NCRenameFile", bundle: nil).instantiateInitialViewController() as? NCRenameFile { - - vcRename.metadata = metadata - vcRename.imagePreview = imageIcon - vcRename.indexPath = indexPath - - let popup = NCPopupViewController(contentController: vcRename, popupWidth: vcRename.width, popupHeight: vcRename.height) - - self.present(popup, animated: true) - } + self.present(UIAlertController.renameFile(metadata: metadata, indexPath: indexPath), animated: true) } ) ) diff --git a/iOSClient/Menu/NCMenuAction.swift b/iOSClient/Menu/NCMenuAction.swift index 6e082b5dab..208693c756 100644 --- a/iOSClient/Menu/NCMenuAction.swift +++ b/iOSClient/Menu/NCMenuAction.swift @@ -201,9 +201,22 @@ extension NCMenuAction { icon: NCUtility().loadImage(named: "rectangle.portrait.and.arrow.right", colors: [NCBrandColor.shared.iconImageColor]), order: order, action: { _ in - let controller = viewController.tabBarController as? NCMainTabBarController - NCActionCenter.shared.openSelectView(items: selectedMetadatas, controller: controller) - completion?() + var fileNameError: NKError? + + for metadata in selectedMetadatas { + if let checkError = FileNameValidator.shared.checkFileName(metadata.fileNameView) { + fileNameError = checkError + break + } + } + + if let fileNameError { + viewController.present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true, completion: nil) + } else { + let controller = viewController.tabBarController as? NCMainTabBarController + NCActionCenter.shared.openSelectView(items: selectedMetadatas, controller: controller) + completion?() + } } ) } diff --git a/iOSClient/Menu/NCViewer+Menu.swift b/iOSClient/Menu/NCViewer+Menu.swift index b9e858967a..9f8de5dcc5 100644 --- a/iOSClient/Menu/NCViewer+Menu.swift +++ b/iOSClient/Menu/NCViewer+Menu.swift @@ -152,18 +152,7 @@ extension NCViewer { title: NSLocalizedString("_rename_", comment: ""), icon: utility.loadImage(named: "text.cursor", colors: [NCBrandColor.shared.iconImageColor]), action: { _ in - - if let vcRename = UIStoryboard(name: "NCRenameFile", bundle: nil).instantiateInitialViewController() as? NCRenameFile { - - vcRename.metadata = metadata - vcRename.disableChangeExt = true - vcRename.imagePreview = imageIcon - vcRename.indexPath = indexPath - - let popup = NCPopupViewController(contentController: vcRename, popupWidth: vcRename.width, popupHeight: vcRename.height) - - viewController.present(popup, animated: true) - } + viewController.present(UIAlertController.renameFile(metadata: metadata, indexPath: indexPath), animated: true) } ) ) diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index f3b43df76e..302d5b54d9 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -451,6 +451,11 @@ class NCGlobal: NSObject { var capabilitySecurityGuardDiagnostics = false + var capabilityForbiddenFileNames: [String] = [] + var capabilityForbiddenFileNameBasenames: [String] = [] + var capabilityForbiddenFileNameCharacters: [String] = [] + var capabilityForbiddenFileNameExtensions: [String] = [] + // MORE NEXTCLOUD APPS let talkSchemeUrl = "nextcloudtalk://" let notesSchemeUrl = "nextcloudnotes://" @@ -464,6 +469,7 @@ class NCGlobal: NSObject { // FORBIDDEN CHARACTERS // + // TODO: Remove this let forbiddenCharacters = ["/", "\\", ":", "\"", "|", "?", "*", "<", ">"] // DIAGNOSTICS CLIENTS diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index 40ed82627a..136d632c07 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -44,7 +44,6 @@ class NCNetworkingE2EEUpload: NSObject { var numChunks: Int = 0 func upload(metadata: tableMetadata, uploadE2EEDelegate: uploadE2EEDelegate? = nil, hudView: UIView?, hud: JGProgressHUD?) async -> NKError { - var metadata = metadata let ocIdTemp = metadata.ocId diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 0aff579d6c..35ee30dc24 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -220,6 +220,7 @@ extension NCNetworking { if fileNameFolder.isEmpty { return completion(NKError()) } + let fileNameFolderUrl = serverUrl + "/" + fileNameFolder NextcloudKit.shared.createFolder(serverUrlFileName: fileNameFolderUrl, account: account) { account, _, _, error in @@ -411,7 +412,6 @@ extension NCNetworking { func renameMetadata(_ metadata: tableMetadata, fileNameNew: String, indexPath: IndexPath, - viewController: UIViewController?, completion: @escaping (_ error: NKError) -> Void) { let metadataLive = NCManageDatabase.shared.getMetadataLivePhoto(metadata: metadata) let fileNameNew = fileNameNew.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/iOSClient/Rename file/NCRenameFile.storyboard b/iOSClient/Rename file/NCRenameFile.storyboard deleted file mode 100644 index a2ac6fa094..0000000000 --- a/iOSClient/Rename file/NCRenameFile.storyboard +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iOSClient/Rename file/NCRenameFile.swift b/iOSClient/Rename file/NCRenameFile.swift deleted file mode 100644 index 66ec9959d9..0000000000 --- a/iOSClient/Rename file/NCRenameFile.swift +++ /dev/null @@ -1,251 +0,0 @@ -// -// NCRenameFile.swift -// Nextcloud -// -// Created by Marino Faggiana on 26/02/21. -// Copyright © 2021 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -import UIKit -import NextcloudKit - -public protocol NCRenameFileDelegate: AnyObject { - func rename(fileName: String, fileNameNew: String) -} - -// optional func -public extension NCRenameFileDelegate { - func rename(fileName: String, fileNameNew: String) {} -} - -class NCRenameFile: UIViewController, UITextFieldDelegate { - - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var previewFile: UIImageView! - @IBOutlet weak var fileNameNoExtension: UITextField! - @IBOutlet weak var point: UILabel! - @IBOutlet weak var ext: UITextField! - @IBOutlet weak var fileNameNoExtensionTrailingContraint: NSLayoutConstraint! - @IBOutlet weak var cancelButton: UIButton! - @IBOutlet weak var renameButton: UIButton! - - let width: CGFloat = 300 - let height: CGFloat = 310 - - var metadata: tableMetadata? - var indexPath: IndexPath = IndexPath() - var fileName: String? - var imagePreview: UIImage? - var disableChangeExt: Bool = false - weak var delegate: NCRenameFileDelegate? - - // MARK: - View Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - - if let metadata = self.metadata { - - if metadata.directory { - titleLabel.text = NSLocalizedString("_rename_folder_", comment: "") - } else { - titleLabel.text = NSLocalizedString("_rename_file_", comment: "") - } - - fileNameNoExtension.text = (metadata.fileNameView as NSString).deletingPathExtension - fileNameNoExtension.delegate = self - fileNameNoExtension.becomeFirstResponder() - - ext.text = metadata.fileExtension - ext.delegate = self - if disableChangeExt { - ext.isEnabled = false - ext.textColor = .lightGray - } - - previewFile.image = imagePreview - previewFile.layer.cornerRadius = 10 - previewFile.layer.masksToBounds = true - - if metadata.directory { - - if imagePreview == nil { - previewFile.image = NCImageCache.images.folder - } - - ext.isHidden = true - point.isHidden = true - fileNameNoExtensionTrailingContraint.constant = 20 - - } else { - - if imagePreview == nil { - previewFile.image = NCImageCache.images.file - } - - fileNameNoExtensionTrailingContraint.constant = 90 - } - - } else if let fileName = self.fileName { - - titleLabel.text = NSLocalizedString("_rename_file_", comment: "") - - fileNameNoExtension.text = (fileName as NSString).deletingPathExtension - fileNameNoExtension.delegate = self - fileNameNoExtension.becomeFirstResponder() - fileNameNoExtensionTrailingContraint.constant = 90 - - ext.text = (fileName as NSString).pathExtension - ext.delegate = self - - if imagePreview == nil { - previewFile.image = NCImageCache.images.file - } else { - previewFile.image = imagePreview - } - previewFile.layer.cornerRadius = 10 - previewFile.layer.masksToBounds = true - } - - cancelButton.setTitle(NSLocalizedString("_cancel_", comment: ""), for: .normal) - cancelButton.setTitleColor(NCBrandColor.shared.brandElement, for: .normal) - renameButton.setTitle(NSLocalizedString("_rename_", comment: ""), for: .normal) - renameButton.setTitleColor(NCBrandColor.shared.brandElement, for: .normal) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if metadata == nil && fileName == nil { - dismiss(animated: true) - } - - fileNameNoExtension.selectAll(nil) - } - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - - textField.resignFirstResponder() - renameFile(textField) - return true - } - - // MARK: - Action - - @IBAction func cancel(_ sender: Any) { - - dismiss(animated: true) - } - - @IBAction func renameFile(_ sender: Any) { - - var fileNameNoExtensionNew = "" - var extNew = "" - var fileNameNew = "" - - if let metadata = self.metadata { - - let extCurrent = (metadata.fileNameView as NSString).pathExtension - - if fileNameNoExtension.text == nil || fileNameNoExtension.text?.count == 0 { - return self.fileNameNoExtension.text = (metadata.fileNameView as NSString).deletingPathExtension - } else { - fileNameNoExtensionNew = fileNameNoExtension.text! - } - - if metadata.directory { - - fileNameNew = fileNameNoExtensionNew - renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: indexPath) - - } else { - - if ext.text == nil || ext.text?.count == 0 { - return self.ext.text = metadata.fileExtension - } else { - extNew = ext.text! - } - - if extNew != extCurrent { - - let message = String(format: NSLocalizedString("_rename_ext_message_", comment: ""), extNew, extCurrent) - let alertController = UIAlertController(title: NSLocalizedString("_rename_ext_title_", comment: ""), message: message, preferredStyle: .alert) - - var title = NSLocalizedString("_use_", comment: "") + " ." + extNew - alertController.addAction(UIAlertAction(title: title, style: .default, handler: { _ in - - fileNameNew = fileNameNoExtensionNew + "." + extNew - self.renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: self.indexPath) - })) - - title = NSLocalizedString("_keep_", comment: "") + " ." + extCurrent - alertController.addAction(UIAlertAction(title: title, style: .default, handler: { _ in - self.ext.text = metadata.fileExtension - })) - - self.present(alertController, animated: true) - - } else { - - fileNameNew = fileNameNoExtensionNew + "." + extNew - renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: indexPath) - } - } - - } else if let fileName = self.fileName { - - if fileNameNoExtension.text == nil || fileNameNoExtension.text?.count == 0 { - return fileNameNoExtension.text = (fileName as NSString).deletingPathExtension - } else if ext.text == nil || ext.text?.count == 0 { - return ext.text = (fileName as NSString).pathExtension - } - - fileNameNew = (fileNameNoExtension.text ?? "") + "." + (ext.text ?? "") - self.dismiss(animated: true) { - self.delegate?.rename(fileName: fileName, fileNameNew: fileNameNew) - } - } - } - - // MARK: - Networking - - func renameMetadata(_ metadata: tableMetadata, fileNameNew: String, indexPath: IndexPath) { - - // verify if already exists - if NCManageDatabase.shared.getMetadata(predicate: NSPredicate(format: "account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, fileNameNew)) != nil { - NCContentPresenter().showError(error: NKError(errorCode: 0, errorDescription: "_rename_already_exists_")) - return - } - - NCActivityIndicator.shared.start() - - NCNetworking.shared.renameMetadata(metadata, fileNameNew: fileNameNew, indexPath: indexPath, viewController: self) { error in - - NCActivityIndicator.shared.stop() - - if error == .success { - - self.dismiss(animated: true) - - } else { - - NCContentPresenter().showError(error: error) - } - } - } -} diff --git a/iOSClient/Scan document/NCUploadScanDocument.swift b/iOSClient/Scan document/NCUploadScanDocument.swift index 47459f55b6..3f0d2cd88c 100644 --- a/iOSClient/Scan document/NCUploadScanDocument.swift +++ b/iOSClient/Scan document/NCUploadScanDocument.swift @@ -321,6 +321,7 @@ extension NCUploadScanDocument: NCCreateFormUploadConflictDelegate { struct UploadScanDocumentView: View { @State var fileName = NCUtilityFileSystem().createFileNameDate("scan", ext: "") + @State var footer = "" @State var password: String = "" @State var isSecuredPassword: Bool = true @State var isTextRecognition: Bool = NCKeychain().textRecognitionStatus @@ -349,7 +350,7 @@ struct UploadScanDocumentView: View { GeometryReader { geo in ZStack(alignment: .top) { List { - Section(header: Text(NSLocalizedString("_file_creation_", comment: ""))) { + Section(header: Text(NSLocalizedString("_file_creation_", comment: "")), footer: Text(footer)) { HStack { Label { if NCUtilityFileSystem().getHomeServer(urlBase: uploadScanDocument.userBaseUrl.urlBase, userId: uploadScanDocument.userBaseUrl.userId) == uploadScanDocument.serverUrl { @@ -383,6 +384,14 @@ struct UploadScanDocumentView: View { TextField(NSLocalizedString("_enter_filename_", comment: ""), text: $fileName) .modifier(TextFieldClearButton(text: $fileName)) .multilineTextAlignment(.trailing) + .onChange(of: fileName) { _ in + if let fileNameError = FileNameValidator.shared.checkFileName(fileName) { + footer = fileNameError.errorDescription + } else { + footer = "" + + } + } } HStack { Group { @@ -414,6 +423,7 @@ struct UploadScanDocumentView: View { .complexModifier { view in view.listRowSeparator(.hidden) } + VStack(spacing: 20) { Toggle(NSLocalizedString("_delete_all_scanned_images_", comment: ""), isOn: $removeAllFiles) .toggleStyle(SwitchToggleStyle(tint: Color(NCBrandColor.shared.brandElement))) @@ -436,7 +446,8 @@ struct UploadScanDocumentView: View { } } } - .buttonStyle(ButtonRounded(disabled: fileName.isEmpty)) + .buttonStyle(ButtonRounded(disabled: fileName.isEmpty || !footer.isEmpty)) + .disabled(fileName.isEmpty || !footer.isEmpty) } Section(header: Text(NSLocalizedString("_quality_image_title_", comment: ""))) { diff --git a/iOSClient/Select/NCSelect.swift b/iOSClient/Select/NCSelect.swift index 78b7846c2c..8fa401cc23 100644 --- a/iOSClient/Select/NCSelect.swift +++ b/iOSClient/Select/NCSelect.swift @@ -147,11 +147,11 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.navigationItem.title = titleCurrentFolder + let folderPath = utilityFileSystem.getFileNamePath("", serverUrl: serverUrl, urlBase: appDelegate.urlBase, userId: appDelegate.userId) - // set the serverUrl - if serverUrl.isEmpty { + if serverUrl.isEmpty || !FileNameValidator.shared.checkFolderPath(folderPath: folderPath) { serverUrl = utilityFileSystem.getHomeServer(urlBase: activeAccount.urlBase, userId: activeAccount.userId) + titleCurrentFolder = NCBrandOptions.shared.brand } // get auto upload folder @@ -159,6 +159,8 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent autoUploadDirectory = NCManageDatabase.shared.getAccountAutoUploadDirectory(urlBase: activeAccount.urlBase, userId: activeAccount.userId, account: activeAccount.account) loadDatasource(withLoadFolder: true) + + self.navigationItem.title = titleCurrentFolder } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -252,7 +254,11 @@ class NCSelect: UIViewController, UIGestureRecognizerDelegate, UIAdaptivePresent viewController.titleCurrentFolder = metadata.fileNameView viewController.serverUrl = serverUrlPush - self.navigationController?.pushViewController(viewController, animated: true) + if let fileNameError = FileNameValidator.shared.checkFileName(metadata.fileNameView) { + present(UIAlertController.warning(message: "\(fileNameError.errorDescription) \(NSLocalizedString("_please_rename_file_", comment: ""))"), animated: true) + } else { + navigationController?.pushViewController(viewController, animated: true) + } } } @@ -265,11 +271,8 @@ extension NCSelect: UICollectionViewDelegate { guard let metadata = dataSource.cellForItemAt(indexPath: indexPath) else { return } if metadata.directory { - pushMetadata(metadata) - } else { - delegate?.dismissSelect(serverUrl: serverUrl, metadata: metadata, type: type, items: items, overwrite: overwrite, copy: false, move: false) self.dismiss(animated: true, completion: nil) } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index b3f262528a..9f5aed61d0 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -944,13 +944,13 @@ "_calendar_contacts_footer_" = "After downloading the profile you can install it from Settings."; "_preview_" = "Preview"; "_crop_" = "Crop"; -"_modify_image_desc_" = "Tap the image for modify"; -"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live effect"; +"_modify_image_desc_" = "Tap on a file to modify or rename."; +"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect"; "_enable_livephoto_" = "Enable Live Photo"; "_disable_livephoto_" = "Disable Live Photo"; -"_undo_modify_" = "Undo the modify"; -"_unauthorizedFilesPasscode_" = "Files cannot be used with an activated passcode"; -"_disableFilesApp_" = "Files cannot be used because it is disabled"; +"_undo_modify_" = "Undo modifying"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; +"_disableFilesApp_" = "Files app cannot be used because it is disabled"; "_reset_application_done_" = "Reset application, done."; "_rename_already_exists_" = "A file with this name already exists"; "_created_" = "Created"; @@ -985,7 +985,7 @@ "_delete_selected_photos_" = "Delete selected photos"; "_media_square_" = "Square grid"; "_media_ratio_" = "Aspect ratio grid"; -"_autoupload_notice_" = "To ensure the proper functioning of the application, it is necessary to enable Background App Refresh. Otherwise, new photos or videos will not be detected when the application is in the background.\n\nAdditionally, please note that the application will not be able to detect new photos and videos if it is manually terminated. When the app is in the background, data transfer may be slower, and new photos and/or videos will generally be detected every 10 minutes, depending on the device’s battery level.\n\nTo verify that the app is functioning correctly, you can use the log file available in Advanced."; +"_autoupload_notice_" = "To ensure the proper functioning of the application, it is necessary to enable Background App Refresh. Otherwise, new photos or videos will not be detected when the application is in the background.\n\nAdditionally, please note that the application will not be able to detect new photos and videos if it is manually terminated. When the app is in the background, data transfer may be slower, and new photos and/or videos will generally be detected every 10 minutes, depending on the device’s battery level.\n\nTo verify that the app is functioning correctly, you can use the log file available in Advanced settings."; "_display_" = "Display"; "_appearance_" = "Appearance"; "_light_" = "Light"; @@ -1090,3 +1090,10 @@ // MARK: Login poll "_poll_desc_" = "Please complete the log in process in your browser"; + +// MARK: File name validator +"_file_name_validator_error_ends_with_space_period_" = "Name ends with a space or a period."; +"_file_name_validator_error_reserved_name_" = "\"%@\" is a forbidden name."; +"_file_name_validator_error_forbidden_file_extension_" = "\".%@\" is a forbidden file extension."; +"_file_name_validator_error_invalid_character_" = "Name contains an invalid character: \"%@\"."; +"_please_rename_file_" = "Please rename the file or folder."; diff --git a/iOSClient/Utility/NCPopupViewController.swift b/iOSClient/Utility/NCPopupViewController.swift index 78053bac0b..8a4fdf1e85 100644 --- a/iOSClient/Utility/NCPopupViewController.swift +++ b/iOSClient/Utility/NCPopupViewController.swift @@ -43,7 +43,7 @@ public class NCPopupViewController: UIViewController { private(set) public var popupHeight: CGFloat? // Background alpha, default is 0.3 - public var backgroundAlpha: CGFloat = 0.3 + public var backgroundAlpha: CGFloat = 0.2 // Background color, default is black public var backgroundColor = UIColor.black