2017-02-08 19:27:57 +08:00
|
|
|
//
|
|
|
|
|
// Utils.swift
|
|
|
|
|
// pass
|
|
|
|
|
//
|
|
|
|
|
// Created by Mingshen Sun on 8/2/2017.
|
|
|
|
|
// Copyright © 2017 Bob Sun. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
2017-02-09 22:12:25 +08:00
|
|
|
import SwiftyUserDefaults
|
2017-02-19 22:10:36 +08:00
|
|
|
import KeychainAccess
|
2017-02-16 00:54:42 +08:00
|
|
|
import UIKit
|
2017-03-16 23:12:31 -07:00
|
|
|
import SVProgressHUD
|
2017-02-08 19:27:57 +08:00
|
|
|
|
|
|
|
|
class Utils {
|
|
|
|
|
static func removeFileIfExists(atPath path: String) {
|
|
|
|
|
let fm = FileManager.default
|
|
|
|
|
do {
|
|
|
|
|
if fm.fileExists(atPath: path) {
|
|
|
|
|
try fm.removeItem(atPath: path)
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
print(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static func removeFileIfExists(at url: URL) {
|
|
|
|
|
removeFileIfExists(atPath: url.path)
|
|
|
|
|
}
|
2017-02-09 22:12:25 +08:00
|
|
|
|
|
|
|
|
static func getLastUpdatedTimeString() -> String {
|
|
|
|
|
var lastUpdatedTimeString = ""
|
|
|
|
|
if let lastUpdatedTime = Defaults[.lastUpdatedTime] {
|
|
|
|
|
let formatter = DateFormatter()
|
2017-02-10 15:32:01 +08:00
|
|
|
formatter.dateStyle = .medium
|
2017-02-09 22:12:25 +08:00
|
|
|
formatter.timeStyle = .short
|
|
|
|
|
lastUpdatedTimeString = formatter.string(from: lastUpdatedTime)
|
|
|
|
|
}
|
|
|
|
|
return lastUpdatedTimeString
|
|
|
|
|
}
|
2017-02-11 22:00:04 +08:00
|
|
|
|
2017-02-20 21:56:23 +08:00
|
|
|
static func generatePassword(length: Int) -> String{
|
2017-03-08 00:57:49 -08:00
|
|
|
switch Defaults[.passwordGeneratorFlavor] {
|
2017-02-20 21:56:23 +08:00
|
|
|
case "Random":
|
|
|
|
|
return randomString(length: length)
|
2017-03-08 00:57:49 -08:00
|
|
|
case "Apple":
|
2017-02-20 21:56:23 +08:00
|
|
|
return Keychain.generatePassword()
|
|
|
|
|
default:
|
|
|
|
|
return randomString(length: length)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-11 22:00:04 +08:00
|
|
|
static func randomString(length: Int) -> String {
|
|
|
|
|
|
|
|
|
|
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
|
let len = UInt32(letters.length)
|
|
|
|
|
|
|
|
|
|
var randomString = ""
|
|
|
|
|
|
|
|
|
|
for _ in 0 ..< length {
|
|
|
|
|
let rand = arc4random_uniform(len)
|
|
|
|
|
var nextChar = letters.character(at: Int(rand))
|
|
|
|
|
randomString += NSString(characters: &nextChar, length: 1) as String
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return randomString
|
|
|
|
|
}
|
2017-02-16 00:54:42 +08:00
|
|
|
|
|
|
|
|
static func alert(title: String, message: String, controller: UIViewController, completion: (() -> Void)?) {
|
2017-03-16 23:12:31 -07:00
|
|
|
SVProgressHUD.dismiss()
|
2017-02-16 00:54:42 +08:00
|
|
|
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
|
|
|
|
|
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
|
|
|
|
|
controller.present(alert, animated: true, completion: completion)
|
|
|
|
|
|
|
|
|
|
}
|
2017-02-17 13:44:25 +08:00
|
|
|
|
|
|
|
|
static func removePGPKeys() {
|
|
|
|
|
removeFileIfExists(atPath: Globals.pgpPublicKeyPath)
|
|
|
|
|
removeFileIfExists(atPath: Globals.pgpPrivateKeyPath)
|
|
|
|
|
Defaults.remove(.pgpKeySource)
|
|
|
|
|
Defaults.remove(.pgpPublicKeyArmor)
|
|
|
|
|
Defaults.remove(.pgpPrivateKeyArmor)
|
|
|
|
|
Defaults.remove(.pgpPrivateKeyURL)
|
|
|
|
|
Defaults.remove(.pgpPublicKeyURL)
|
2017-02-19 22:26:37 +08:00
|
|
|
Utils.removeKeychain(name: ".pgpKeyPassphrase")
|
2017-02-17 13:44:25 +08:00
|
|
|
}
|
2017-02-19 22:10:36 +08:00
|
|
|
|
|
|
|
|
static func getPasswordFromKeychain(name: String) -> String? {
|
|
|
|
|
let keychain = Keychain(service: "me.mssun.passforios")
|
|
|
|
|
do {
|
|
|
|
|
return try keychain.getString(name)
|
|
|
|
|
} catch {
|
|
|
|
|
print(error)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-06 12:49:00 -08:00
|
|
|
static func addPasswordToKeychain(name: String, password: String?) {
|
2017-02-19 22:10:36 +08:00
|
|
|
let keychain = Keychain(service: "me.mssun.passforios")
|
|
|
|
|
keychain[name] = password
|
|
|
|
|
}
|
|
|
|
|
static func removeKeychain(name: String) {
|
|
|
|
|
let keychain = Keychain(service: "me.mssun.passforios")
|
|
|
|
|
do {
|
|
|
|
|
try keychain.remove(name)
|
|
|
|
|
} catch {
|
|
|
|
|
print(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static func removeAllKeychain() {
|
|
|
|
|
let keychain = Keychain(service: "me.mssun.passforios")
|
|
|
|
|
do {
|
|
|
|
|
try keychain.removeAll()
|
|
|
|
|
} catch {
|
|
|
|
|
print(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-23 17:56:12 +08:00
|
|
|
static func copyToPasteboard(textToCopy: String?, expirationTime: Double = 45) {
|
|
|
|
|
guard textToCopy != nil else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
UIPasteboard.general.string = textToCopy
|
|
|
|
|
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + expirationTime) {
|
|
|
|
|
let pasteboardString: String? = UIPasteboard.general.string
|
|
|
|
|
if textToCopy == pasteboardString {
|
|
|
|
|
UIPasteboard.general.string = ""
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-26 00:58:18 +08:00
|
|
|
static func attributedPassword(plainPassword: String) -> NSAttributedString{
|
|
|
|
|
let attributedPassword = NSMutableAttributedString.init(string: plainPassword)
|
|
|
|
|
// draw all digits in the password into red
|
|
|
|
|
// draw all punctuation characters in the password into blue
|
|
|
|
|
for (index, element) in plainPassword.unicodeScalars.enumerated() {
|
|
|
|
|
if NSCharacterSet.decimalDigits.contains(element) {
|
|
|
|
|
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.red, range: NSRange(location: index, length: 1))
|
2017-03-24 01:34:45 +08:00
|
|
|
} else if !NSCharacterSet.letters.contains(element) {
|
2017-02-26 00:58:18 +08:00
|
|
|
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.blue, range: NSRange(location: index, length: 1))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return attributedPassword
|
|
|
|
|
}
|
2017-03-08 00:57:49 -08:00
|
|
|
static func initDefaultKeys() {
|
|
|
|
|
if Defaults[.passwordGeneratorFlavor] == "" {
|
|
|
|
|
Defaults[.passwordGeneratorFlavor] = "Random"
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-08 19:27:57 +08:00
|
|
|
}
|
2017-02-10 00:49:49 +08:00
|
|
|
|
|
|
|
|
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
|
|
|
|
|
extension FileManager {
|
|
|
|
|
|
|
|
|
|
/// This method calculates the accumulated size of a directory on the volume in bytes.
|
|
|
|
|
///
|
|
|
|
|
/// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
|
|
|
|
|
/// accumulating the overall sum on the way. The resulting value is roughly equivalent with the amount of bytes
|
|
|
|
|
/// that would become available on the volume if the directory would be deleted.
|
|
|
|
|
///
|
|
|
|
|
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
|
|
|
|
|
/// directories, hard links, ...).
|
|
|
|
|
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
|
|
|
|
|
|
|
|
|
|
// We'll sum up content size here:
|
|
|
|
|
var accumulatedSize = UInt64(0)
|
|
|
|
|
|
|
|
|
|
// prefetching some properties during traversal will speed up things a bit.
|
|
|
|
|
let prefetchedProperties = [
|
|
|
|
|
URLResourceKey.isRegularFileKey,
|
|
|
|
|
URLResourceKey.fileAllocatedSizeKey,
|
|
|
|
|
URLResourceKey.totalFileAllocatedSizeKey,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
// The error handler simply signals errors to outside code.
|
|
|
|
|
var errorDidOccur: Error?
|
|
|
|
|
let errorHandler: (URL, Error) -> Bool = { _, error in
|
|
|
|
|
errorDidOccur = error
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We have to enumerate all directory contents, including subdirectories.
|
|
|
|
|
let enumerator = self.enumerator(at: directoryURL,
|
|
|
|
|
includingPropertiesForKeys: prefetchedProperties,
|
|
|
|
|
options: FileManager.DirectoryEnumerationOptions(),
|
|
|
|
|
errorHandler: errorHandler)
|
|
|
|
|
precondition(enumerator != nil)
|
|
|
|
|
|
|
|
|
|
// Start the traversal:
|
|
|
|
|
for item in enumerator! {
|
|
|
|
|
let contentItemURL = item as! NSURL
|
|
|
|
|
|
|
|
|
|
// Bail out on errors from the errorHandler.
|
|
|
|
|
if let error = errorDidOccur { throw error }
|
|
|
|
|
|
|
|
|
|
let resourceValueForKey: (URLResourceKey) throws -> NSNumber? = { key in
|
|
|
|
|
var value: AnyObject?
|
|
|
|
|
try contentItemURL.getResourceValue(&value, forKey: key)
|
|
|
|
|
return value as? NSNumber
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the type of this item, making sure we only sum up sizes of regular files.
|
|
|
|
|
guard let isRegularFile = try resourceValueForKey(URLResourceKey.isRegularFileKey) else {
|
|
|
|
|
preconditionFailure()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard isRegularFile.boolValue else {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
|
|
|
|
|
// This includes metadata, compression (on file system level) and block size.
|
|
|
|
|
var fileSize = try resourceValueForKey(URLResourceKey.totalFileAllocatedSizeKey)
|
|
|
|
|
|
|
|
|
|
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
|
|
|
|
|
// This value should always be available.
|
|
|
|
|
fileSize = try fileSize ?? resourceValueForKey(URLResourceKey.fileAllocatedSizeKey)
|
|
|
|
|
|
|
|
|
|
guard let size = fileSize else {
|
|
|
|
|
preconditionFailure("huh? NSURLFileAllocatedSizeKey should always return a value")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We're good, add up the value.
|
|
|
|
|
accumulatedSize += size.uint64Value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Bail out on errors from the errorHandler.
|
|
|
|
|
if let error = errorDidOccur { throw error }
|
|
|
|
|
|
|
|
|
|
// We finally got it.
|
|
|
|
|
return accumulatedSize
|
|
|
|
|
}
|
|
|
|
|
}
|