diff --git a/core/util.go b/core/util.go index 6ae9ea1..4537020 100644 --- a/core/util.go +++ b/core/util.go @@ -2,6 +2,7 @@ package core import ( "errors" + "fmt" "os" "strings" "time" @@ -17,11 +18,11 @@ import ( "github.com/rwinkhart/rcw/wrappers" ) -// WriteEntry writes entryData to an encrypted file at realPath. +// WriteEntry writes decSlice to an encrypted file at realPath. // If the entry contains an updated password, an age file is also created. // Leave rcwPassword nil to use RCW demonization. func WriteEntry(realPath string, decSlice []string, passwordIsNew bool, rcwPassword []byte) error { - err := os.WriteFile(realPath, crypt.EncryptBytes([]byte(strings.Join(decSlice, "\n")), rcwPassword), 0600) + err := os.WriteFile(realPath, crypt.EncryptBytes([]byte(strings.Join(clampTrailingWhitespace(decSlice), "\n")), rcwPassword), 0600) if err != nil { return errors.New("unable to write to file: " + err.Error()) } @@ -45,11 +46,6 @@ func WriteEntry(realPath string, decSlice []string, passwordIsNew bool, rcwPassw // EntryRefresh re-encrypts all libmutton entries with a new password // and optimizes each entry to ensure they are as slim as possible. -// This includes stripping trailing whitespace/newlines/carriage returns -// from each field and running each note through ClampTrailingWhitespace -// to ensure each note line is optimized as possible without breaking -// Markdown formatting. -// Be sure to verify passwords before using as input for this function!! func EntryRefresh(oldRCWPassword, newRCWPassword []byte, removeOldDir bool) error { // ensure global.EntryRoot+"-new" and global.EntryRoot-"old" do not exist dirEnds := []string{"-new", "-old"} @@ -78,51 +74,27 @@ func EntryRefresh(oldRCWPassword, newRCWPassword []byte, removeOldDir bool) erro // decrypt, optimize, and re-encrypt each entry for _, vanityPath := range entries { + fmt.Println(vanityPath) // useful for CLI clients to track what entry caused a panic if one is corrupt realPath := global.GetRealPath(vanityPath) encBytes, err := os.ReadFile(realPath) if err != nil { return errors.New("unable to open \"" + realPath + "\" for decryption: " + err.Error()) } decBytes, err := wrappers.Decrypt(encBytes, oldRCWPassword) - decryptedEntry := strings.Split(string(decBytes), "\n") if err != nil { return err } - // strip trailing whitespace... - fieldsLength := len(decryptedEntry) - if fieldsLength < 4 { - fieldsMain := back.RemoveTrailingEmptyStrings(decryptedEntry) - // ...from each non-note field - for i, line := range fieldsMain { - fieldsMain[i] = strings.TrimRight(line, " \t\r\n") - } - decryptedEntry = fieldsMain - } else { - fieldsMain := decryptedEntry[:4] - fieldsNote := back.RemoveTrailingEmptyStrings(decryptedEntry[4:]) - // ...from each non-note field - for i, line := range fieldsMain { - fieldsMain[i] = strings.TrimRight(line, " \t\r\n") - } - // ...and from each note line (preserve Markdown formatting) - ClampTrailingWhitespace(fieldsNote) - // re-combine fields - decryptedEntry = append(fieldsMain, fieldsNote...) - } + // split & optimize entry + decSlice := clampTrailingWhitespace(strings.Split(string(decBytes), "\n")) // re-encrypt the entry with the new password - encBytes = wrappers.Encrypt([]byte(strings.Join(decryptedEntry, "\n")), newRCWPassword) + encBytes = wrappers.Encrypt([]byte(strings.Join(decSlice, "\n")), newRCWPassword) // write the entry to the new directory if err = os.WriteFile(global.EntryRoot+"-new"+strings.ReplaceAll(vanityPath, "/", global.PathSeparator), encBytes, 0600); err != nil { return errors.New("unable to write to file: " + err.Error()) } - - // generate new sanity check file - if err = RCWSanityCheckGen(newRCWPassword); err != nil { - return err - } } // swap the new directory with the old one @@ -133,38 +105,49 @@ func EntryRefresh(oldRCWPassword, newRCWPassword []byte, removeOldDir bool) erro return errors.New("unable to rename new directory: " + err.Error()) } + // generate new sanity check file + if err = RCWSanityCheckGen(newRCWPassword); err != nil { + return err + } + return nil } -// ClampTrailingWhitespace strips trailing newlines, carriage returns, and tabs from each line in a note. -// Additionally, it removes single trailing spaces and truncates multiple trailing spaces to two (for Markdown formatting). -func ClampTrailingWhitespace(note []string) { - for i, line := range note { - // remove trailing tabs, carriage returns, and newlines - line = strings.TrimRight(line, "\t\r\n") +// clampTrailingWhitespace ensures the provided decSlice contains no trailing blank lines. +// If decSlice contains a note, it strips trailing newlines, carriage returns, and tabs from +// each line in the note. Additionally, it removes single trailing spaces and truncates +// multiple trailing spaces to two (for Markdown formatting). +func clampTrailingWhitespace(decSlice []string) []string { + decSlice = back.RemoveTrailingEmptyStrings(decSlice) + if len(decSlice) >= 4 { + for i, noteLine := range decSlice[4:] { + // remove trailing tabs, carriage returns, and newlines + noteLine = strings.TrimRight(noteLine, "\t\r\n") - // determine the number of trailing spaces in the trimmed line - var endSpacesCount int - for j := len(line) - 1; j >= 0; j-- { - if line[j] != ' ' { - break + // determine the number of trailing spaces in the trimmed line + var endSpacesCount int + for j := len(noteLine) - 1; j >= 0; j-- { + if noteLine[j] != ' ' { + break + } + endSpacesCount++ } - endSpacesCount++ - } - // remove single spaces, truncate multiple spaces (leave two for Markdown formatting) - switch endSpacesCount { - case 0: - // no trailing spaces - note[i] = line - case 1: - // remove the single trailing space - note[i] = strings.TrimRight(line, " ") - default: - // truncate the trailing spaces to two - note[i] = line[:len(line)-endSpacesCount+2] + // remove single spaces, truncate multiple spaces (leave two for Markdown formatting) + switch endSpacesCount { + case 0: + // no trailing spaces + decSlice[i] = noteLine + case 1: + // remove the single trailing space + decSlice[i] = strings.TrimRight(noteLine, " ") + default: + // truncate the trailing spaces to two + decSlice[i] = noteLine[:len(noteLine)-endSpacesCount+2] + } } } + return decSlice } // EntryAddPrecheck ensures the directory meant to contain a new diff --git a/go.mod b/go.mod index e6697e5..6f6d29e 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.25.6 require ( github.com/pkg/sftp v1.13.10 github.com/pquerna/otp v1.5.0 - github.com/rwinkhart/go-boilerplate v0.1.1 + github.com/rwinkhart/go-boilerplate v0.2.2 github.com/rwinkhart/rcw v0.2.4 - golang.org/x/crypto v0.46.0 + golang.org/x/crypto v0.47.0 golang.org/x/sys v0.40.0 ) diff --git a/go.sum b/go.sum index 7092b6b..cfdabe5 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,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.1.1 h1:C+yyscGWeqvNpR91iw0i91zqX0LcxnCr1VOyT7iM4JY= -github.com/rwinkhart/go-boilerplate v0.1.1/go.mod h1:/NVRKGslU20E5xU5YOgXzWxA6aa94BMtv5MtHRTb5Ek= +github.com/rwinkhart/go-boilerplate v0.2.2 h1:SVHTAQU+HWFivtUnDBcfrgClJV5ZmHyFS7/uERh7NKU= +github.com/rwinkhart/go-boilerplate v0.2.2/go.mod h1:/NVRKGslU20E5xU5YOgXzWxA6aa94BMtv5MtHRTb5Ek= github.com/rwinkhart/go-winio v0.1.0 h1:b72agLW+dETGmhR3VbcbwnStfgKfc5AfgJOXBJDkaHg= github.com/rwinkhart/go-winio v0.1.0/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= github.com/rwinkhart/peercred-mini v0.1.2 h1:4cGWDbv0whvLeVvbUdx84V/9p+2fS+DEXgrA1KxlRFo= @@ -26,9 +26,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=