Skip to content

Commit

Permalink
[feature/suffix-protection] File extension / suffix protection (#1298)
Browse files Browse the repository at this point in the history
* - NamingViewController: add option .requiredFileExtension that enforces a particular suffix when set
- CreateDocumentAction: take advantage of NamingViewController.requiredFileExtension to prevent changing file suffix for new files

* - ThemeCSSTextField: add support for requiring a file extension (.requiredFileExtension)
- NamingViewController: remove delegate code for .requiredFileExtension and use ThemeCSSTextField.requiredFileExtension instead
- StaticTableViewRow: add support for requiring a file extension (.requiredFileExtension) based on ThemeCSSTextField.requiredFileExtension
- ScanViewController: use StaticTableViewRow.requiredFileExtension to prevent deletion of required file extension

* - ios-sdk update: fixes crash (finding #1 in #1298)
- ScanViewController, NamingViewController: when enforcing file suffixes: prevent choice of filenames consisting just of a suffix (since these would be hidden files) by disabling the primary action button

* Calens changelog updated

---------

Co-authored-by: felix-schwarz <felix-schwarz@users.noreply.github.com>
Co-authored-by: Matthias Hühne <github@hosy.de>
  • Loading branch information
3 people committed Dec 7, 2023
1 parent e092190 commit 1756b13
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ class CreateDocumentAction: Action {
}
})

documentNameViewController.requiredFileExtension = fileType.extension
documentNameViewController.navigationItem.title = "Pick a name".localized

navigationViewController.pushViewController(documentNameViewController, animated: true)
Expand Down
15 changes: 13 additions & 2 deletions ownCloud/Client/Actions/Scanner/ScanViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,11 @@ class ScanViewController: StaticTableViewController {
didSet {
if let fileName = fileNameRow?.value as? NSString {
fileNameRow?.value = fileName.deletingPathExtension + "." + (exportFormat?.suffix ?? "")
fileNameRow?.requiredFileExtension = exportFormat?.suffix

if let textField = fileNameRow?.textField, textField.isFirstResponder {
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.beginningOfDocument)
}
}

guard let oneFilePerPageRow = oneFilePerPageRow else { return }
Expand Down Expand Up @@ -315,7 +320,9 @@ class ScanViewController: StaticTableViewController {
self.isModalInPresentation = true
self.navigationItem.title = "Scan".localized
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(ScanViewController.cancel))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(ScanViewController.save))

let saveBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(ScanViewController.save))
self.navigationItem.rightBarButtonItem = saveBarButtonItem

self.core = core
self.targetFolderItem = item
Expand Down Expand Up @@ -353,7 +360,7 @@ class ScanViewController: StaticTableViewController {
// Save section

// - Name
fileNameRow = StaticTableViewRow(textFieldWithAction: { [weak self] (row, textField, type) in
fileNameRow = StaticTableViewRow(textFieldWithAction: { [weak self, weak saveBarButtonItem] (row, textField, type) in
self?.navigationItem.rightBarButtonItem?.isEnabled = ((row.value as? String)?.count ?? 0) > 0

if type == .didBegin, let nameTextField = textField as? UITextField {
Expand All @@ -369,6 +376,10 @@ class ScanViewController: StaticTableViewController {
nameTextField.selectedTextRange = nameTextField.textRange(from: nameTextField.beginningOfDocument, to: nameTextField.endOfDocument)
}
}

if let fileName = (textField as? UITextField)?.text, let requiredFileExtension = row.requiredFileExtension {
saveBarButtonItem?.isEnabled = (fileName.count > (requiredFileExtension.count + 1))
}
}, placeholder: "Name".localized, value: fileName ?? "", keyboardType: .default, autocorrectionType: .no, enablesReturnKeyAutomatically: true, returnKeyType: .default, identifier: "name", accessibilityLabel: "Name".localized)
saveSection.add(row: fileNameRow!)
self.addSection(saveSection)
Expand Down
20 changes: 16 additions & 4 deletions ownCloudAppShared/Client/User Interface/NamingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@
import UIKit
import ownCloudSDK

public typealias StringValidatorResult = (Bool, String?, String?)
public typealias StringValidatorHandler = (String) -> StringValidatorResult
public typealias StringValidatorResult = (Bool, String?, String?) // (validationPassed, validationErrorTitle, validationErrorMessage)
public typealias StringValidatorHandler = (_ stringToCheck: String) -> StringValidatorResult

open class NamingViewController: UIViewController {
weak open var item: OCItem?
weak open var core: OCCore?
open var completion: (String?, NamingViewController) -> Void
open var stringValidator: StringValidatorHandler?
open var defaultName: String?
open var requiredFileExtension: String?

private var blurView: UIVisualEffectView

Expand All @@ -37,7 +38,7 @@ open class NamingViewController: UIViewController {
private var thumbnailImageView: ResourceViewHost

private var nameContainer: UIView
private var nameTextField: UITextField
private var nameTextField: ThemeCSSTextField

private var textfieldTopAnchorConstraint: NSLayoutConstraint
private var textfieldCenterYAnchorConstraint: NSLayoutConstraint
Expand Down Expand Up @@ -152,6 +153,7 @@ open class NamingViewController: UIViewController {

// Name textfield
nameTextField.translatesAutoresizingMaskIntoConstraints = false
nameTextField.requiredFileExtension = requiredFileExtension
nameContainer.addSubview(nameTextField)
NSLayoutConstraint.activate([
nameTextField.heightAnchor.constraint(equalToConstant: 40),
Expand Down Expand Up @@ -267,7 +269,9 @@ open class NamingViewController: UIViewController {
}

@objc open func textfieldDidChange(_ sender: UITextField) {
if sender.text != "" {
let filename = sender.text

if filename != "", requiredFileExtension == nil || ((requiredFileExtension != nil) && filename != ".\(requiredFileExtension!)") {
doneButton?.isEnabled = true
} else {
doneButton?.isEnabled = false
Expand Down Expand Up @@ -351,7 +355,15 @@ extension NamingViewController: UITextFieldDelegate {
} else {
textField.selectedTextRange = nameTextField.textRange(from: nameTextField.beginningOfDocument, to: nameTextField.endOfDocument)
}
}

public func textFieldShouldClear(_ textField: UITextField) -> Bool {
if let requiredFileExtension {
textField.text = "." + requiredFileExtension
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.beginningOfDocument)
return false
}
return true
}

open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,21 @@ open class StaticTableViewRow : NSObject, UITextFieldDelegate {
return true
}

public func textFieldShouldClear(_ textField: UITextField) -> Bool {
if let requiredFileExtension {
textField.text = "." + requiredFileExtension
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.beginningOfDocument)
return false
}
return true
}

open var requiredFileExtension: String? {
didSet {
(textField as? ThemeCSSTextField)?.requiredFileExtension = requiredFileExtension
}
}

// MARK: - Labels
convenience public init(label: String, accessoryView: UIView? = nil, identifier: String? = nil) {
self.init()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,20 @@

import UIKit

public extension UITextInput {
func range(from textRange: UITextRange) -> NSRange {
let startOffset = offset(from: beginningOfDocument, to: textRange.start)
let endOffset = offset(from: beginningOfDocument, to: textRange.end)

return NSRange(location: startOffset, length: endOffset-startOffset+1)
}
}

public class ThemeCSSTextField: UITextField, Themeable {
private var hasRegistered : Bool = false

open var requiredFileExtension: String?

override open func didMoveToWindow() {
super.didMoveToWindow()

Expand All @@ -41,4 +52,26 @@ public class ThemeCSSTextField: UITextField, Themeable {
open func applyThemeCollection(theme: Theme, collection: ThemeCollection, event: ThemeEvent) {
self.applyThemeCollection(collection)
}

public override func shouldChangeText(in range: UITextRange, replacementText: String) -> Bool {
if let requiredFileExtension, let previousText = text,
let textRange = Range(self.range(from: range), in: previousText) {
let newName = previousText.replacingCharacters(in: textRange, with: replacementText)
return (newName as NSString).pathExtension == requiredFileExtension || (newName == ".\(requiredFileExtension)")
}

return super.shouldChangeText(in: range, replacementText: replacementText)
}

public override var selectedTextRange: UITextRange? {
didSet {
if let requiredFileExtension {
if let selectedTextRange,
let maxAllowedPosition = position(from: endOfDocument, offset: -requiredFileExtension.count-1),
compare(selectedTextRange.end, to: maxAllowedPosition) == .orderedDescending {
self.selectedTextRange = textRange(from: selectedTextRange.start, to: maxAllowedPosition)
}
}
}
}
}

0 comments on commit 1756b13

Please sign in to comment.