package main import ( "errors" "flag" "fmt" "log" "net/http" "strconv" ) const maxPasswordLength = 512 // parseParam parses a positive integer form field. If the field is absent or // empty, defaultVal is returned. If present but invalid or out of [1, max], // an error is returned. func parseParam(r *http.Request, name string, defaultVal, max int) (int, error) { v := r.FormValue(name) if v == "" { return defaultVal, nil } n, err := strconv.Atoi(v) if err != nil { return 0, fmt.Errorf("%s: must be an integer", name) } if n <= 0 { return 0, fmt.Errorf("%s: must be greater than zero", name) } if n > max { return 0, fmt.Errorf("%s: must be at most %d", name, max) } return n, nil } type passwordData struct { Length int Digits int Symbols int NoUpper bool DenyRepeat bool Password string } var defaults passwordData func generatePassword(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.Error(w, "404 not found.", http.StatusNotFound) return } switch r.Method { case "GET": password, err := generate(defaults.Length, defaults.Digits, defaults.Symbols, false, false) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Error: " + err.Error())) return } w.WriteHeader(http.StatusOK) w.Write([]byte(password)) case "POST": var d passwordData if err := r.ParseForm(); err != nil { w.WriteHeader(http.StatusUnprocessableEntity) w.Write([]byte(fmt.Sprintf("ParseForm() err: %v", err))) return } var err error var validationErr error d.Length, err = parseParam(r, "length", defaults.Length, maxPasswordLength) validationErr = errors.Join(validationErr, err) d.Digits, err = parseParam(r, "digits", defaults.Digits, maxPasswordLength) validationErr = errors.Join(validationErr, err) d.Symbols, err = parseParam(r, "symbols", defaults.Symbols, maxPasswordLength) validationErr = errors.Join(validationErr, err) if validationErr != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(validationErr.Error())) return } d.NoUpper = r.FormValue("noupper") == "on" d.DenyRepeat = r.FormValue("denyrepeat") == "on" var password string password, err = generate(d.Length, d.Digits, d.Symbols, d.NoUpper, !d.DenyRepeat) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Error: " + err.Error())) return } w.WriteHeader(http.StatusOK) w.Write([]byte(password)) default: w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Sorry, only GET and POST methods are supported.")) } } func checkHealth(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("Status:OK")) } func main() { // Set defaults portPtr := flag.Int("listen", 8080, "Specify on which port to listen") lengthPtr := flag.Int("length", 16, "Specify the password length") digitsPtr := flag.Int("digits", 2, "Specify the the number of digits in the password") symbolsPtr := flag.Int("symbols", 2, "Specify the the number of symbols in the password") flag.Parse() defaults.Length = *lengthPtr defaults.Digits = *digitsPtr defaults.Symbols = *symbolsPtr http.HandleFunc("/", generatePassword) http.HandleFunc("/health", checkHealth) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*portPtr), nil)) }