diff --git a/core/copy.go b/core/copy.go index e36030f..da1b963 100644 --- a/core/copy.go +++ b/core/copy.go @@ -15,64 +15,65 @@ import ( // CopyArgument copies a field from an entry to the clipboard. func CopyArgument(targetLocation string, field int) error { - if isFile, _, err := back.TargetIsFile(targetLocation, true, 2); isFile { - if err != nil { - return err - } - - decryptedEntry, err := crypt.DecryptFileToSlice(targetLocation) - if err != nil { - return errors.New("unable to decrypt entry: " + err.Error()) - } - var copySubject string // will store data to be copied - - // ensure field exists in entry - if len(decryptedEntry) > field { - - // ensure field is not empty - if decryptedEntry[field] == "" { - return errors.New("field is empty") - } - - if field != 2 { - copySubject = decryptedEntry[field] - } else { // TOTP mode - var secret string // stores secret for TOTP generation - var forSteam bool // indicates whether to generate TOTP in Steam format - - if strings.HasPrefix(decryptedEntry[2], "steam@") { - secret = decryptedEntry[2][6:] - forSteam = true - } else { - secret = decryptedEntry[2] - } - - fmt.Println("Clipboard will be kept up to date with the current TOTP code until this process is closed") - - for { // keep token copied to clipboard, refresh on 30-second intervals - currentTime := time.Now() - token, err := GenTOTP(secret, currentTime, forSteam) - if err != nil { - return err - } - err = copyString(true, token) - if err != nil { - return err - } - // sleep until next 30-second interval - time.Sleep(time.Duration(30-(currentTime.Second()%30)) * time.Second) - } - } - } else { - return errors.New("field does not exist in entry") - } - - // copy field to clipboard, launch clipboard clearing process - err = copyString(false, copySubject) - if err != nil { - return err - } + // ensure targetLocation exists and is a file + _, err := back.TargetIsFile(targetLocation, true) + if err != nil { + return err } + + decryptedEntry, err := crypt.DecryptFileToSlice(targetLocation) + if err != nil { + return errors.New("unable to decrypt entry: " + err.Error()) + } + var copySubject string // will store data to be copied + + // ensure field exists in entry + if len(decryptedEntry) > field { + + // ensure field is not empty + if decryptedEntry[field] == "" { + return errors.New("field is empty") + } + + if field != 2 { + copySubject = decryptedEntry[field] + } else { // TOTP mode + var secret string // stores secret for TOTP generation + var forSteam bool // indicates whether to generate TOTP in Steam format + + if strings.HasPrefix(decryptedEntry[2], "steam@") { + secret = decryptedEntry[2][6:] + forSteam = true + } else { + secret = decryptedEntry[2] + } + + fmt.Println("Clipboard will be kept up to date with the current TOTP code until this process is closed") + + for { // keep token copied to clipboard, refresh on 30-second intervals + currentTime := time.Now() + token, err := GenTOTP(secret, currentTime, forSteam) + if err != nil { + return err + } + err = copyString(true, token) + if err != nil { + return err + } + // sleep until next 30-second interval + time.Sleep(time.Duration(30-(currentTime.Second()%30)) * time.Second) + } + } + } else { + return errors.New("field does not exist in entry") + } + + // copy field to clipboard, launch clipboard clearing process + err = copyString(false, copySubject) + if err != nil { + return err + } + return nil } diff --git a/core/edit.go b/core/edit.go index 7adcf0d..df22923 100644 --- a/core/edit.go +++ b/core/edit.go @@ -9,8 +9,11 @@ import ( // GetOldEntryData decrypts and returns old entry data (with all required lines present). func GetOldEntryData(targetLocation string, field int) ([]string, error) { - // ensure targetLocation exists - back.TargetIsFile(targetLocation, true, 2) + // ensure targetLocation exists and is a file + _, err := back.TargetIsFile(targetLocation, true) + if err != nil { + return nil, err + } // read old entry data decryptedEntry, err := crypt.DecryptFileToSlice(targetLocation) diff --git a/core/init.go b/core/init.go index 9add3b9..c00c422 100644 --- a/core/init.go +++ b/core/init.go @@ -18,12 +18,12 @@ import ( func LibmuttonInit(inputCB func(prompt string) string, clientSpecificIniData [][3]string, rcwPassphrase []byte, preserveOldConfigDir bool) error { r := strings.ToLower(inputCB("Configure SSH settings (for synchronization)? (y/N)")) if len(r) > 0 && r[0] == 'y' { - // ensure ssh key file exists + // ensure ssh key file exists (and is a file) fallbackSSHKey := back.Home + global.PathSeparator + ".ssh" + global.PathSeparator + "id_ed25519" sshKeyPath := cmp.Or(back.ExpandPathWithHome(inputCB(back.AnsiBold+"Note:"+back.AnsiReset+" Only key-based authentication is supported (keys may optionally be passphrase-protected).\n The remote server must already be in your ~"+global.PathSeparator+".ssh"+global.PathSeparator+"known_hosts file.\n\nSSH private identity file path (falls back to \""+fallbackSSHKey+"\"):")), fallbackSSHKey) - sshKeyIsFile, _, _ := back.TargetIsFile(sshKeyPath, false, 0) // error is ignored because errorOnFail is false - if !sshKeyIsFile { - return errors.New("SSH identity file not found: " + sshKeyPath) + _, err := back.TargetIsFile(sshKeyPath, true) + if err != nil { + return errors.New("unable to find SSH identity file: " + err.Error()) } // get other ssh info from user diff --git a/core/utilitiesMisc.go b/core/utilitiesMisc.go index 0412222..1feed01 100644 --- a/core/utilitiesMisc.go +++ b/core/utilitiesMisc.go @@ -156,15 +156,15 @@ func ClampTrailingWhitespace(note []string) { // Returns: statusCode (0 = success, 1 = target location already exists, 2 = containing directory is invalid). func EntryAddPrecheck(targetLocation string) (uint8, error) { // ensure target location does not already exist - _, isAccessible, _ := back.TargetIsFile(targetLocation, false, 0) // error is ignored because errorOnFail is false + isAccessible, _ := back.TargetIsFile(targetLocation, false) // error is ignored because dir/file status is irrelevant if isAccessible { return 1, errors.New("target location already exists") } - // ensure target containing directory exists and is a directory (not a file) + // ensure target containing directory exists and is not a file containingDir := targetLocation[:strings.LastIndex(targetLocation, global.PathSeparator)] - isFile, isAccessible, _ := back.TargetIsFile(containingDir, false, 1) // error is ignored because errorOnFail is false - if isFile || !isAccessible { - return 2, errors.New("\"" + containingDir + "\" is not a valid containing directory") + _, err := back.TargetIsFile(containingDir, false) + if err != nil { + return 2, errors.New("\"" + containingDir + "\" is not a valid containing directory: " + err.Error()) } return 0, nil } diff --git a/global/init.go b/global/init.go index 91de0ca..79c29ef 100644 --- a/global/init.go +++ b/global/init.go @@ -24,7 +24,7 @@ func DirInit(preserveOldConfigDir bool) (string, error) { // remove existing config directory (if it exists and not in append mode) if !preserveOldConfigDir { - _, isAccessible, _ := back.TargetIsFile(ConfigDir, false, 1) // error is ignored because errorOnFail is false + isAccessible, _ := back.TargetIsFile(ConfigDir, false) // error is ignored because dir/file status is irrelevant if isAccessible { err = os.RemoveAll(ConfigDir) if err != nil { diff --git a/go.mod b/go.mod index 430ebde..e8985fa 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.3 require ( github.com/pkg/sftp v1.13.9 github.com/pquerna/otp v1.5.0 - github.com/rwinkhart/go-boilerplate v0.0.0-20250601181619-e38a47784de0 + github.com/rwinkhart/go-boilerplate v0.0.0-20250601184813-71b5056ca6b3 github.com/rwinkhart/rcw v0.2.0 golang.design/x/clipboard v0.7.0 // only for Android builds golang.org/x/crypto v0.38.0 diff --git a/go.sum b/go.sum index 6dd9c8a..3acc164 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/rwinkhart/go-boilerplate v0.0.0-20250601181619-e38a47784de0 h1:Zuwn0er30tMYrBXD1n4nM3mkGhrLFAdn3sZyMHkxx2I= -github.com/rwinkhart/go-boilerplate v0.0.0-20250601181619-e38a47784de0/go.mod h1:cnzIF45I0FCOvE4YIB+26pLCUx2kWyY2llKYZruNaRY= +github.com/rwinkhart/go-boilerplate v0.0.0-20250601184813-71b5056ca6b3 h1:QgmV+Om1Z1Ix+nO6k9RRIzLpevkeeyrmCspIKysvsc4= +github.com/rwinkhart/go-boilerplate v0.0.0-20250601184813-71b5056ca6b3/go.mod h1:cnzIF45I0FCOvE4YIB+26pLCUx2kWyY2llKYZruNaRY= github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410 h1:NhHwFM3Pgm6zRUfFKvi0p5ndjfFbVWsRwmmhyFlG4PE= github.com/rwinkhart/go-winio-easy-pipe-handles v0.0.0-20250407031321-96994a0e8410/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/rwinkhart/peercred-mini v0.1.0 h1:TiS6u8cEWzW55S9X4iVpU72Iuy/NG6rMUJMtUEVEFLw= diff --git a/syncclient/client.go b/syncclient/client.go index 4aabfc1..fcf502f 100644 --- a/syncclient/client.go +++ b/syncclient/client.go @@ -416,15 +416,15 @@ func folderSync(folders []string) error { // store the full local path of the folder folderFullPath := global.TargetLocationFormat(folder) - // check if folder already exists - isFile, isAccessible, _ := back.TargetIsFile(folderFullPath, false, 1) // error is ignored because errorOnFail is false + // check if target path already exists + isAccessible, _ := back.TargetIsFile(folderFullPath, false) // error is ignored because dir/file status is irrelevant - if !isFile && !isAccessible { + if !isAccessible { err := os.MkdirAll(folderFullPath, 0700) if err != nil { return errors.New("unable to create folder (" + folder + "): " + err.Error()) } - } else if isFile { + } else { return errors.New("unable to create folder (" + folder + "): a file with the same name already exists") } } diff --git a/synccommon/common.go b/synccommon/common.go index dfb1bc1..d15ff65 100644 --- a/synccommon/common.go +++ b/synccommon/common.go @@ -66,10 +66,13 @@ func ShearLocal(targetLocationIncomplete, clientDeviceID string) (string, bool, targetLocationComplete := global.TargetLocationFormat(targetLocationIncomplete) var isFile bool if !onServer { // error if target does not exist on client, needed because os.RemoveAll does not return an error if target does not exist - isFile, _, err = back.TargetIsFile(targetLocationComplete, true, 0) - if err != nil { + isAccessible, err := back.TargetIsFile(targetLocationComplete, true) + if !isAccessible { return "", false, err } + if err == nil { // fails if target is a directory, so no error indicates a file + isFile = true + } } err = os.RemoveAll(targetLocationComplete) if err != nil { @@ -92,13 +95,16 @@ func RenameLocal(oldLocationIncomplete, newLocationIncomplete string, verifyOldL newLocation := global.TargetLocationFormat(newLocationIncomplete) if verifyOldLocationExists { - back.TargetIsFile(oldLocation, true, 0) + isAccessible, _ := back.TargetIsFile(oldLocation, true) // error is ignored because dir/file status is irrelevant + if !isAccessible { + return errors.New("old target (" + oldLocation + ") does not exist") + } } // ensure newLocation does not exist - _, isAccessible, _ := back.TargetIsFile(newLocation, false, 0) // error is ignored because errorOnFail is false + isAccessible, _ := back.TargetIsFile(newLocation, true) // error is ignored because dir/file status is irrelevant if isAccessible { - return errors.New("target already exists: " + newLocation) + return errors.New("new target (" + newLocation + ") already exists") } // rename oldLocation to newLocation