changeset 8:698c4a3d758d

Some code clean-up.
author David Barts <n5jrn@me.com>
date Fri, 23 Sep 2022 20:59:52 -0700
parents f245b9a53495
children 72619175004e
files src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt src/main/kotlin/name/blackcap/passman/Database.kt src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt src/main/kotlin/name/blackcap/passman/Encryption.kt src/main/kotlin/name/blackcap/passman/Entry.kt src/main/kotlin/name/blackcap/passman/Generate.kt src/main/kotlin/name/blackcap/passman/Hashing.kt src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt src/main/kotlin/name/blackcap/passman/ListSubcommand.kt src/main/kotlin/name/blackcap/passman/Main.kt src/main/kotlin/name/blackcap/passman/MergeSubcommand.kt src/main/kotlin/name/blackcap/passman/ReadSubcommand.kt src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt
diffstat 13 files changed, 116 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -4,38 +4,46 @@
 import kotlin.system.exitProcess
 
 class CreateSubcommand(): Subcommand() {
+    private companion object {
+        const val GENERATE = "generate"
+        const val HELP = "help"
+        const val LENGTH = "length"
+        const val SYMBOLS = "symbols"
+        const val VERBOSE = "verbose"
+    }
     private lateinit var commandLine: CommandLine
 
     override fun run(args: Array<String>) {
         val options = Options().apply {
-            addOption("g", "generate", false, "Use password generator.")
-            addOption("l", "length", true, "Length of generated password.")
-            addOption("s", "symbols", false, "Use symbol characters in generated password.")
-            addOption("v", "verbose", false, "Print the generated password.")
+            addOption("g", GENERATE, false, "Use password generator.")
+            addOption("h", HELP, false, "Print this help message.")
+            addOption("l", LENGTH, true, "Length of generated password.")
+            addOption("s", SYMBOLS, false, "Use symbol characters in generated password.")
+            addOption("v", VERBOSE, false, "Print the generated password.")
         }
         try {
             commandLine = DefaultParser().parse(options, args)
         } catch (e: ParseException) {
             die(e.message ?: "syntax error", 2)
         }
-        if (commandLine.hasOption("help")) {
-            HelpFormatter().printHelp("$SHORTNAME createJv", options)
+        if (commandLine.hasOption(HELP)) {
+            HelpFormatter().printHelp("$SHORTNAME create", options)
             exitProcess(0)
         }
         checkArguments()
         val db = Database.open()
 
-        val entry = if (commandLine.hasOption("generate")) {
-            val rawLength = commandLine.getOptionValue("length")
+        val entry = if (commandLine.hasOption(GENERATE)) {
+            val rawLength = commandLine.getOptionValue(LENGTH)
             val length = try {
                 rawLength?.toInt() ?: DEFAULT_GENERATED_LENGTH
             } catch (e: NumberFormatException) {
                 die("${see(rawLength)} - invalid length")
                 -1  /* will never happen */
             }
-            val symbols = commandLine.hasOption("symbols")
-            val verbose = commandLine.hasOption("verbose")
-            Entry.withGeneratedPassword(length, symbols, verbose)
+            Entry.withGeneratedPassword(length,
+                commandLine.hasOption(SYMBOLS),
+                commandLine.hasOption(VERBOSE))
         } else {
             Entry.withPromptedPassword()
         }
@@ -52,7 +60,7 @@
         }
 
         try {
-            if (entry.notes.isBlank()) {
+            if (entry.notes.isNullOrBlank()) {
                 db.connection.prepareStatement("insert into passwords (id, name, username, password, created) values (?, ?, ?, ?, ?)")
                     .use {
                         it.setLong(1, id)
@@ -81,10 +89,10 @@
 
     private fun checkArguments(): Unit {
         var bad = false
-        if (!commandLine.hasOption("generate")) {
-            for (option in listOf<String>("length", "symbols", "verbose")) {
+        if (!commandLine.hasOption(GENERATE)) {
+            for (option in listOf<String>(LENGTH, SYMBOLS, VERBOSE)) {
                 if (commandLine.hasOption(option)) {
-                    error("--$option requires --generate")
+                    error("--$option requires --$GENERATE")
                     bad = true
                 }
             }
--- a/src/main/kotlin/name/blackcap/passman/Database.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/Database.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -114,14 +114,24 @@
     fun makeKey(name: String): Long = Hashing.hash(encryption.encryptFromString0(name.lowercase()))
 }
 
-public fun ResultSet.getDecryptedString(columnIndex: Int, encryption: Encryption) =
-    encryption.decryptToString(getBytes(columnIndex))
+public fun ResultSet.getDecryptedString(columnIndex: Int, encryption: Encryption): String? {
+    return encryption.decryptToString(getBytes(columnIndex) ?: return null)
+}
 
-public fun ResultSet.getDecrypted(columnIndex: Int, encryption: Encryption) =
-    encryption.decrypt(getBytes(columnIndex))
+public fun ResultSet.getDecrypted(columnIndex: Int, encryption: Encryption): CharArray? {
+    return encryption.decrypt(getBytes(columnIndex) ?: return null)
+}
 
-public fun PreparedStatement.setEncryptedString(columnIndex: Int, value: String, encryption: Encryption) =
-    setBytes(columnIndex, encryption.encryptFromString(value))
+public fun PreparedStatement.setEncryptedString(columnIndex: Int, value: String?, encryption: Encryption) =
+    if (value == null) {
+        setNull(columnIndex, Types.BLOB)
+    } else {
+        setBytes(columnIndex, encryption.encryptFromString(value))
+    }
 
-public fun PreparedStatement.setEncrypted(columnIndex: Int, value: CharArray, encryption: Encryption) =
-    setBytes(columnIndex, encryption.encrypt(value))
+public fun PreparedStatement.setEncrypted(columnIndex: Int, value: CharArray?, encryption: Encryption) =
+    if (value == null) {
+        setNull(columnIndex, Types.BLOB)
+    } else {
+        setBytes(columnIndex, encryption.encrypt(value))
+    }
--- a/src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -20,4 +20,4 @@
         }
         exitProcess(if (errors > 0) 1 else 0)
     }
-}
\ No newline at end of file
+}
--- a/src/main/kotlin/name/blackcap/passman/Encryption.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/Encryption.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -94,4 +94,4 @@
 fun CharBuffer.zero() {
     clear()
     array().clear()
-}
\ No newline at end of file
+}
--- a/src/main/kotlin/name/blackcap/passman/Entry.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/Entry.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -4,7 +4,7 @@
 import kotlin.reflect.KProperty
 import kotlin.reflect.full.declaredMemberProperties
 
-class Entry(val name: String, val username: String, val password: CharArray, val notes: String,
+class Entry(val name: String, val username: String, val password: CharArray, val notes: String?,
             val created: Date? = null, val modified: Date? = null, val accessed: Date? = null) {
 
     companion object {
@@ -59,7 +59,7 @@
 
     fun printLong(redactPassword: String? = null) {
         print(redactPassword)
-        println("Notes: $notes")
+        println("Notes: ${notes ?: "(none)"}")
         printDate("Created", created)
         printDate("Modified", modified)
         printDate("Accessed", accessed)
@@ -77,5 +77,4 @@
             println(ISO8601.format(date))
         }
     }
-
-}
\ No newline at end of file
+}
--- a/src/main/kotlin/name/blackcap/passman/Generate.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/Generate.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -44,12 +44,12 @@
     }
 
     /* scramble them */
-    for (i in 0 until length) {
-        val j = randomizer.nextInt(length)
-        if (i != j) {
-            val temp = generated[i]
-            generated[i] = generated[j]
-            generated[j] = temp
+    for (j in 0 until length) {
+        val k = randomizer.nextInt(length)
+        if (j != k) {
+            val temp = generated[j]
+            generated[j] = generated[k]
+            generated[k] = temp
         }
     }
 
--- a/src/main/kotlin/name/blackcap/passman/Hashing.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/Hashing.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -31,4 +31,4 @@
         order(ByteOrder.LITTLE_ENDIAN)
         getLong(hash.size - 8)
     }
-}
\ No newline at end of file
+}
--- a/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -12,4 +12,4 @@
         println("list         List records.")
         println("merge        Merge passwords in from another PassMan database.")
     }
-}
\ No newline at end of file
+}
--- a/src/main/kotlin/name/blackcap/passman/ListSubcommand.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/ListSubcommand.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -63,6 +63,9 @@
             HelpFormatter().printHelp("$SHORTNAME list", options)
             exitProcess(0)
         }
+        if (commandLine.args.isNotEmpty()) {
+            error("unexpected trailing arguments")
+        }
 
         STRING_OPTIONS.forEach {
             commandLine.getOptionValues(it.name)?.forEach { value ->
@@ -122,8 +125,8 @@
             var count = 0;
             while (results.next()) {
                 val entry = Entry(
-                    name = results.getDecryptedString(1, db.encryption),
-                    username = results.getDecryptedString(2, db.encryption),
+                    name = results.getDecryptedString(1, db.encryption)!!,
+                    username = results.getDecryptedString(2, db.encryption)!!,
                     password = REDACTED,
                     notes = results.getDecryptedString(3, db.encryption),
                     created = results.getDate(4),
--- a/src/main/kotlin/name/blackcap/passman/Main.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/Main.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -32,8 +32,6 @@
         .lowercase()
         .replaceFirstChar { it.titlecase(Locale.getDefault()) }
     Class.forName("$MAIN_PACKAGE.${shortName}Subcommand") as? Class<Subcommand>
-    /* val ret = Class.forName("$MAIN_PACKAGE.$shortName")
-    if (ret.isInstance(Subcommand::class.java)) { ret as Class<Subcommand> } else { null } */
 } catch (e: ClassNotFoundException) {
     null
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/kotlin/name/blackcap/passman/MergeSubcommand.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -0,0 +1,7 @@
+package name.blackcap.passman
+
+class MergeSubcommand(): Subcommand() {
+    override fun run(args: Array<String>) {
+        error("not yet implemented")
+    }
+}
--- a/src/main/kotlin/name/blackcap/passman/ReadSubcommand.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/ReadSubcommand.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -4,25 +4,31 @@
 import kotlin.system.exitProcess
 
 class ReadSubcommand(): Subcommand() {
+    private companion object {
+        const val CLIPBOARD = "clipboard"
+        const val HELP = "help"
+        const val LONG = "long"
+        const val ALT_SB = "\u001b[?1049h"
+        const val NORM_SB = "\u001b[?1049l"
+        const val CLEAR = "\u001b[H\u001b[2J"
+    }
     private lateinit var commandLine: CommandLine
 
     override fun run(args: Array<String>) {
         val options = Options().apply {
-            addOption("c", "clipboard", false, "Copy username and password into clipboard.")
-            addOption("h", "help", false, "Print this help message.")
-            addOption("l", "long", false, "Long format listing.")
+            addOption("c", CLIPBOARD, false, "Copy username and password into clipboard.")
+            addOption("h", HELP, false, "Print this help message.")
+            addOption("l", LONG, false, "Long format listing.")
         }
         try {
             commandLine = DefaultParser().parse(options, args)
         } catch (e: ParseException) {
             die(e.message ?: "syntax error", 2)
         }
-        if (commandLine.hasOption("help")) {
+        if (commandLine.hasOption(HELP)) {
             HelpFormatter().printHelp("$SHORTNAME read", options)
             exitProcess(0)
         }
-        val clipboard = commandLine.hasOption("clipboard")
-        val long = commandLine.hasOption("long")
         if (commandLine.args.isEmpty()) {
             error("expecting site name")
         }
@@ -40,25 +46,28 @@
                 die("no record matches ${see(nameIn)}")
             }
             val entry = Entry(
-                name = result.getDecryptedString(1, db.encryption),
-                username = result.getDecryptedString(2, db.encryption),
-                password = result.getDecrypted(3, db.encryption),
+                name = result.getDecryptedString(1, db.encryption)!!,
+                username = result.getDecryptedString(2, db.encryption)!!,
+                password = result.getDecrypted(3, db.encryption)!!,
                 notes = result.getDecryptedString(4, db.encryption),
                 created = result.getDate(5),
                 modified = result.getDate(6),
                 accessed = result.getDate(7)
             )
             try {
-                val redaction = if (clipboard) { "(in clipboard)" } else { null }
-                if (long) {
+                print(ALT_SB + CLEAR)
+                val redaction = if (commandLine.hasOption(CLIPBOARD)) { "(in clipboard)" } else { null }
+                if (commandLine.hasOption(LONG)) {
                     entry.printLong(redaction)
                 } else {
                     entry.print(redaction)
                 }
-                if (clipboard) {
+                if (commandLine.hasOption(CLIPBOARD)) {
                     writeToClipboard(entry.password)
                 }
+                name.blackcap.passman.readLine("Press ENTER to continue: ")
             } finally {
+                print(CLEAR + NORM_SB)
                 entry.password.clear()
             }
         }
--- a/src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt	Tue Sep 20 21:54:32 2022 -0700
+++ b/src/main/kotlin/name/blackcap/passman/UpdateSubcommand.kt	Fri Sep 23 20:59:52 2022 -0700
@@ -12,13 +12,15 @@
     private lateinit var nameIn: String
     private var id by Delegates.notNull<Long>()
     private var length by Delegates.notNull<Int>()
-    private var generate by Delegates.notNull<Boolean>()
-    private var allowSymbols by Delegates.notNull<Boolean>()
-    private var verbose by Delegates.notNull<Boolean>()
     private val fields = StringBuilder()
     private val fieldValues = mutableListOf<Any?>()
 
     private companion object {
+        const val GENERATE = "generate"
+        const val HELP = "help"
+        const val LENGTH = "length"
+        const val SYMBOLS = "symbols"
+        const val VERBOSE = "verbose"
         const val NULL_SPECIFIED = "."
         val NULLABLE_FIELDS = setOf<String>("notes")
         val SENSITIVE_FIELDS = setOf<String>("password")
@@ -27,23 +29,27 @@
     override fun run(args: Array<String>) {
         parseArguments(args)
         checkDatabase()
-        update()
+        try {
+            update()
+        } finally {
+            cleanUp()
+        }
     }
 
     private fun parseArguments(args: Array<String>) {
         val options = Options().apply {
-            addOption("g", "generate", false, "Use password generator.")
-            addOption("h", "help", false, "Print this help message.")
-            addOption("l", "length", true, "Length of generated password.")
-            addOption("s", "symbols", false, "Use symbol characters in generated password.")
-            addOption("v", "verbose", false, "Print the generated password.")
+            addOption("g", GENERATE, false, "Use password generator.")
+            addOption("h", HELP, false, "Print this help message.")
+            addOption("l", LENGTH, true, "Length of generated password.")
+            addOption("s", SYMBOLS, false, "Use symbol characters in generated password.")
+            addOption("v", VERBOSE, false, "Print the generated password.")
         }
         try {
             commandLine = DefaultParser().parse(options, args)
         } catch (e: ParseException) {
             die(e.message ?: "syntax error", 2)
         }
-        if (commandLine.hasOption("help")) {
+        if (commandLine.hasOption(HELP)) {
             HelpFormatter().printHelp("$SHORTNAME update", options)
             exitProcess(0)
         }
@@ -51,25 +57,22 @@
         db = Database.open()
         nameIn = commandLine.args[0]
         id = db.makeKey(nameIn)
-        length = commandLine.getOptionValue("length").let { rawLength ->
+        length = commandLine.getOptionValue(LENGTH)?.let { rawLength ->
             try {
-                rawLength?.toInt() ?: DEFAULT_GENERATED_LENGTH
+                rawLength.toInt()
             } catch (e: NumberFormatException) {
                 die("${see(rawLength)} - invalid length")
                 -1  /* will never happen */
             }
-        }
-        generate = commandLine.hasOption("generate")
-        allowSymbols = commandLine.hasOption("symbols")
-        verbose = commandLine.hasOption("verbose")
+        } ?: DEFAULT_GENERATED_LENGTH
     }
 
     private fun checkArguments(): Unit {
         var bad = false
-        if (!commandLine.hasOption("generate")) {
-            for (option in listOf<String>("length", "symbols", "verbose")) {
+        if (!commandLine.hasOption(GENERATE)) {
+            for (option in listOf<String>(LENGTH, SYMBOLS, VERBOSE)) {
                 if (commandLine.hasOption(option)) {
-                    error("--$option requires --generate")
+                    error("--$option requires --$GENERATE")
                     bad = true
                 }
             }
@@ -99,7 +102,7 @@
 
     private fun update(): Unit {
         updateOne("username")
-        if (generate) {
+        if (commandLine.hasOption(GENERATE)) {
             generatePassword()
         } else {
             updateOne("password")
@@ -127,6 +130,10 @@
         }
     }
 
+    private fun cleanUp(): Unit {
+        fieldValues.forEach { if (it is CharArray) { it.clear() } }
+    }
+
     private fun updateOne(name: String): Unit {
         val prompt = name.replaceFirstChar { it.titlecase(Locale.getDefault()) } + ": "
         val value: Any? = if (name in SENSITIVE_FIELDS) {
@@ -162,8 +169,8 @@
     }
 
     private fun generatePassword(): Unit {
-        val newPassword = generate(length, allowSymbols)
-        if (verbose) {
+        val newPassword = generate(length, commandLine.hasOption(SYMBOLS))
+        if (commandLine.hasOption(VERBOSE)) {
             printPassword(newPassword)
         }
         addOne("password", newPassword)