updates
sophuwu sophie@sophuwu.com
Mon, 28 Jul 2025 13:49:40 +0200
2 files changed,
283 insertions(+),
119 deletions(-)
M
flags/flags.go
→
flags/flags.go
@@ -4,19 +4,79 @@ import (
"fmt" "git.sophuwu.com/gophuwu/parsers" "os" + "slices" ) type flag struct { Name string Short string - HelpMsg string Type string + Help []string Default interface{} Value interface{} } -var flags = make(map[string]flag) -var shortFlags = make(map[byte]string) +func (f flag) String() string { + var s string + if len(f.Short) == 1 { + s += fmt.Sprintf("-%s, ", f.Short) + } else { + s += fmt.Sprintf(" ") + } + s += fmt.Sprintf("--%s ", f.Name) + if f.Type == "bool" { + s += fmt.Sprintf("\n") + } else { + s += fmt.Sprintf("<%s>\n", f.Type) + } + if len(f.Help) > 0 { + s += fmt.Sprintf("\t%s ", f.Help[0]) + if f.Default != nil && !(f.Type == "string" && f.Default.(string) == "") { + s += fmt.Sprintf("(default: %v)\n", f.Default) + } else { + s += fmt.Sprintf("\n") + } + for _, v := range f.Help[1:] { + s += fmt.Sprintf("\t%s\n", v) + } + } + return s +} + +var FlagList = []flag{ + { + Name: "help", + Short: "h", + Help: []string{"Show this help message"}, + Type: "bool", + Default: false, + }, +} + +var FlagMap = map[string]*flag{ + "help": &(FlagList[0]), +} +var shortFlags = map[byte]string{ + 'h': "help", +} + +func AddHelp(flag string, helpMsg string) error { + f, ok := FlagMap[flag] + if !ok { + return fmt.Errorf("flag %s does not exist", flag) + } + f.Help = append(f.Help, helpMsg) + return nil +} + +func NewNewFlagWithHandler(handler func(err error)) func(name, short, helpMsg string, defaultValue interface{}) { + return func(name, short, helpMsg string, defaultValue interface{}) { + err := NewFlag(name, short, helpMsg, defaultValue) + if err != nil { + handler(err) + } + } +} // NewFlag creates a new flag with the given name, help message, and default value. // It returns an error if the flag name is invalid, if the flag already exists,@@ -28,49 +88,43 @@ func NewFlag(name, short, helpMsg string, defaultValue interface{}) error {
if len(name) == 0 { return fmt.Errorf("flag name cannot be empty") } - if _, exists := flags[name]; exists { + if _, exists := FlagMap[name]; exists { return fmt.Errorf("flag %s already exists", name) } if defaultValue == nil { return fmt.Errorf("default value for flag %s cannot be nil", name) } - var f flag - f.Name = name - f.HelpMsg = helpMsg if len(short) > 1 { - return fmt.Errorf("short flag must be a single character") + return fmt.Errorf("short flag must be a single character, or empty. %s is invalid", short) } + var shrt *byte = nil if len(short) == 1 { - shrt := short[0] - if _, exists := shortFlags[shrt]; exists { + b := short[0] + shrt = &b + if _, exists := shortFlags[b]; exists { return fmt.Errorf("short flag %s already exists", short) } - shortFlags[shrt] = name - f.Short = short } - switch defaultValue.(type) { - case string: - f.Type = "string" - break - case int: - f.Type = "int" - break - case bool: - f.Type = "bool" - break - case float64: - f.Type = "float64" - break - default: - return fmt.Errorf("unsupported flag type for %s", name) + t := fmt.Sprintf("%T", defaultValue) + if !slices.Contains([]string{"string", "int", "bool", "float64"}, t) { + return fmt.Errorf("unsupported type %T in default value. Supported types are: string, int, bool, float64", defaultValue) + } + FlagList = append(FlagList, flag{ + Name: name, + Short: short, + Help: []string{helpMsg}, + Type: t, + Default: defaultValue, + }) + if shrt != nil { + shortFlags[*shrt] = name } - f.Default = defaultValue - flags[name] = f + FlagMap[name] = &FlagList[len(FlagList)-1] return nil } func getFlag(name, t string) (interface{}, error) { - f, exists := flags[name] + f, exists := FlagMap[name] if !exists { return nil, fmt.Errorf("flag %s does not exist", name) }@@ -119,7 +173,7 @@ }
var v string var vv byte var i, j int - var f flag + var f *flag var ok bool var err error@@ -146,17 +200,12 @@ for i = 0; i < len(args); i++ {
v = args[i] if len(v) > 2 && v[0] == '-' && v[1] == '-' { v = v[2:] - if v == "help" { - PrintHelp() - os.Exit(0) - } - f, ok = flags[v] + f, ok = FlagMap[v] if !ok { return fmt.Errorf("unknown flag: %s", v) } if f.Type == "bool" { f.Value = !f.Default.(bool) - flags[f.Name] = f continue } i++@@ -180,34 +229,21 @@ }
if err != nil { return fmt.Errorf("error parsing flag %s: %v", f.Name, err) } - flags[f.Name] = f } } + if ok, err = GetBoolFlag("help"); err != nil { + return fmt.Errorf("error getting help flag: %v", err) + } else if ok { + PrintHelp() + os.Exit(0) + } return nil } func PrintHelp() { fmt.Printf("Usage: %s [options]\n", os.Args[0]) fmt.Println("Options:") - fmt.Println(" -h --help\n\tShow this help message") - for _, f := range flags { - fmt.Printf(" ") - if len(f.Short) == 1 { - fmt.Printf("-%s, ", f.Short) - } else { - fmt.Printf(" ") - } - fmt.Printf("--%s ", f.Name) - if f.Type == "bool" { - fmt.Printf("\n") - } else { - fmt.Printf("<%s>\n", f.Type) - } - fmt.Printf("\t%s ", f.HelpMsg) - if f.Default != nil { - fmt.Printf("(default: %v)\n", f.Default) - } else { - fmt.Printf("\n") - } + for _, f := range FlagList { + fmt.Print(f.String()) } }
M
hashpw/hashpw.go
→
hashpw/hashpw.go
@@ -1,9 +1,12 @@
package main import ( + "crypto/md5" "crypto/sha1" "crypto/sha256" + "crypto/sha3" "crypto/sha512" + "encoding/base32" "encoding/base64" "encoding/hex" "errors"@@ -12,7 +15,9 @@ "git.sophuwu.com/gophuwu/flags"
"git.sophuwu.com/gophuwu/parsers" "golang.org/x/term" "hash" + "io" "os" + "strings" ) func fatal(err error, msg string) {@@ -22,60 +27,123 @@ os.Exit(1)
} } +func ErrHand(err error) { + fmt.Printf("Fatal Error: %s\n", err.Error()) + os.Exit(1) +} + func init() { - err := flags.NewFlag("algorithm", "a", "hash with sha1, sha256 or sha512", "sha256") - fatal(err, "could not add algorithm flag") - err = flags.NewFlag("encoder", "e", "encode with base64, base64url, hex or raw", "hex") - fatal(err, "could not add encoder flag") - err = flags.NewFlag("output", "o", "output file to write the hashed password to", "stdout") - fatal(err, "could not add output flag") - err = flags.NewFlag("newline", "n", "add a newline to the end of the output", false) - fatal(err, "could not add show flag") - err = flags.NewFlag("show", "s", "show input when typing", false) - fatal(err, "could not add show flag") - err = flags.ParseArgs() + newFlag := flags.NewNewFlagWithHandler(ErrHand) + newFlag("algorithm", "a", "hash algorithm to use, -l to see list", "sha256") + newFlag("encoder", "e", "text encoder to use, -l to see list", "hex") + newFlag("list", "l", "list of available algorithms and encoders", false) + newFlag("output", "o", "write output to file instead of stdout", "stdout") + newFlag("newline", "n", "add a newline to the end of the output, auto/yes/no", "auto") + newFlag("show", "s", "do not hide password on manual input", false) + newFlag("password", "p", "use <string> as password instead of reading stdin", "") + newFlag("file", "f", "hash a file instead of a password", "") + err := flags.ParseArgs() fatal(err, "could not parse flags") } +var encoders = map[string]func(src []byte) []byte{ + "base32": func(src []byte) []byte { + return []byte(base32.StdEncoding.EncodeToString(src)) + }, + "base64": func(src []byte) []byte { + return []byte(base64.StdEncoding.EncodeToString(src)) + }, + "base64url": func(src []byte) []byte { + return []byte(base64.URLEncoding.EncodeToString(src)) + }, + "hex": func(src []byte) []byte { + return []byte(hex.EncodeToString(src)) + }, + "raw": func(src []byte) []byte { + return src + }, +} +var shortEncoders = map[string]string{ + "32": "base32", + "64": "base64", + "url": "base64url", + "16": "hex", + "b": "raw", +} + +var autoNewline = true + func getEnc() func(src []byte) []byte { encoder, err := flags.GetStringFlag("encoder") fatal(err, "could not get encoder flag") - switch encoder { - case "base64": - return func(src []byte) []byte { - return []byte(base64.StdEncoding.EncodeToString(src)) + encoder = strings.ToLower(encoder) + if s, ok := shortEncoders[encoder]; ok { + encoder = s + } + autoNewline = encoder != "raw" && autoNewline + if enc, ok := encoders[encoder]; ok { + return enc + } + return nil +} + +const algosList = "md5 sha1 sha256 sha256_224 sha512 sha512_224 sha512_256 sha512_384 sha3_224 sha3_256 sha3_384 sha3_512" + +var fmtSPad string = "%-5s" + +func init() { + var h func() hash.Hash + var ok bool + for algo := range algosListShort { + if h, ok = algos[algo]; !ok { + fatal(errors.New("unknown algorithm: "+algo), "invalid algorithm") } - case "base64url": - return func(src []byte) []byte { - return []byte(base64.URLEncoding.EncodeToString(src)) - } - case "hex": - return func(src []byte) []byte { - return []byte(hex.EncodeToString(src)) + algos[algosListShort[algo]] = h + } + var fmtNameLength = 5 + for s := range algos { + if len(s) > fmtNameLength { + fmtNameLength = len(s) } - case "raw": - return func(src []byte) []byte { - return src + } + for s := range encoders { + if len(s) > fmtNameLength { + fmtNameLength = len(s) } - default: - fatal(errors.New("unknown encoder: "+encoder), "invalid encoder") } - return nil + fmtNameLength += 1 + fmtSPad = fmt.Sprintf("%%-%ds", fmtNameLength) +} + +var algosListShort = map[string]string{ + "sha1": "1", + "sha512_384": "3", + "sha512": "5", +} + +var algos = map[string]func() hash.Hash{ + "md5": func() hash.Hash { return md5.New() }, + "sha1": func() hash.Hash { return sha1.New() }, + "sha256": func() hash.Hash { return sha256.New() }, + "sha256_224": func() hash.Hash { return sha256.New224() }, + "sha512": func() hash.Hash { return sha512.New() }, + "sha512_224": func() hash.Hash { return sha512.New512_224() }, + "sha512_384": func() hash.Hash { return sha512.New384() }, + "sha512_256": func() hash.Hash { return sha512.New512_256() }, + "sha3_224": func() hash.Hash { return sha3.New224() }, + "sha3_256": func() hash.Hash { return sha3.New256() }, + "sha3_384": func() hash.Hash { return sha3.New384() }, + "sha3_512": func() hash.Hash { return sha3.New512() }, } func getHashFunc() hash.Hash { algorithm, err := flags.GetStringFlag("algorithm") fatal(err, "could not get algorithm flag") - switch algorithm { - case "sha1": - return sha1.New() - case "sha256": - return sha256.New() - case "sha512": - return sha512.New() - default: - fatal(errors.New("unknown algorithm: "+algorithm), "invalid algorithm") + algorithm = strings.ReplaceAll(algorithm, "-", "_") + if h, ok := algos[algorithm]; ok { + return h() } + fatal(errors.New("unknown algorithm: "+algorithm), "invalid algorithm") return nil }@@ -83,6 +151,7 @@ func getOutFunc() func(w []byte) error {
output, err := flags.GetStringFlag("output") fatal(err, "could not get output flag") if output == "stdout" { + autoNewline = autoNewline && term.IsTerminal(1) return func(w []byte) error { _, e := os.Stdout.Write(w) return e@@ -111,39 +180,98 @@ fatal(errors.New("invalid output: "+output), "invalid output")
return nil } -func main() { +func getInput() []byte { var err error - - hashFunc := getHashFunc() - enc := getEnc() - out := getOutFunc() + var s string + var password []byte + s, err = flags.GetStringFlag("file") + fatal(err, "could not get file flag") + if s != "" { + password, err = os.ReadFile(s) + fatal(err, "could not read file") + if len(password) == 0 { + fatal(errors.New("file is empty"), "invalid file") + } + return password + } + s, err = flags.GetStringFlag("password") + fatal(err, "could not get password flag") + if s != "" { + return []byte(s) + } + if !term.IsTerminal(0) { + password, err = io.ReadAll(os.Stdin) + fatal(err, "could not read from stdin") + return password + } - var line bool - line, err = flags.GetBoolFlag("newline") - fatal(err, "could not get newline flag") var show bool show, err = flags.GetBoolFlag("show") fatal(err, "could not get show flag") - var password []byte - - fmt.Print("Enter password to hash: ") if show { + fmt.Print("Enter password to hash: ") password, err = parsers.ReadLine(os.Stdin) fatal(err, "could not read password") - } else { - password, err = term.ReadPassword(0) - fmt.Println() - fatal(err, "could not read password") - fmt.Print("Enter password again: ") - var pas []byte - pas, err = term.ReadPassword(0) - fmt.Println() - fatal(err, "could not read password") - if string(pas) != string(password) { - fmt.Println("Passwords do not match, please try again.") - os.Exit(1) + return password + } + fmt.Print("Enter password to hash: ") + password, err = term.ReadPassword(0) + fmt.Println() + fatal(err, "could not read password") + fmt.Print("Enter password again: ") + var pas []byte + pas, err = term.ReadPassword(0) + fmt.Println() + fatal(err, "could not read password") + if string(pas) != string(password) { + fmt.Println("Passwords do not match, please try again.") + os.Exit(1) + } + return password +} + +func pad(s string, l int) string { + if len(s) >= l { + return s + } + return fmt.Sprintf(fmt.Sprintf("%%-%ds", l), s) +} + +func listAlgos() { + ok, err := flags.GetBoolFlag("list") + fatal(err, "could not get list flag") + if !ok { + return + } + fmt.Println("Available algorithms (short):") + var short string + var algo string + for _, algo = range strings.Split(algosList, " ") { + if short, ok = algosListShort[algo]; ok { + fmt.Printf(" - "+fmtSPad+" (%s)\n", algo, short) + continue } + fmt.Printf(" - "+fmtSPad+"\n", algo) } + fmt.Println("\nAvailable encoders (short):") + for sh, lg := range shortEncoders { + fmt.Printf(" - "+fmtSPad+" (%s)\n", lg, sh) + } + os.Exit(0) +} + +func main() { + listAlgos() + + var err error + hashFunc := getHashFunc() + enc := getEnc() + out := getOutFunc() + + var line string + line, err = flags.GetStringFlag("newline") + fatal(err, "could not get newline flag") + password := getInput() if len(password) == 0 { fatal(errors.New("password cannot be empty"), "invalid password") }@@ -156,7 +284,7 @@ fatal(errors.New("could not write all bytes to hash function"), "invalid password")
} encHash := hashFunc.Sum(nil) encHash = enc(encHash) - if line { + if line == "yes" || (line == "auto" && autoNewline) { encHash = append(encHash, '\n') } err = out(encHash)