mirror of
https://github.com/jimeh/rands.git
synced 2026-02-19 03:16:39 +00:00
247 lines
6.5 KiB
Go
247 lines
6.5 KiB
Go
package rands
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
lowerChars = "abcdefghijklmnopqrstuvwxyz"
|
|
numericChars = "0123456789"
|
|
lowerNumericChars = lowerChars + numericChars
|
|
upperNumericChars = upperChars + numericChars
|
|
alphabeticChars = upperChars + lowerChars
|
|
alphanumericChars = alphabeticChars + numericChars
|
|
dnsLabelChars = lowerNumericChars + "-"
|
|
)
|
|
|
|
var (
|
|
errNonASCIIAlphabet = fmt.Errorf(
|
|
"%w: alphabet contains non-ASCII characters", errBase,
|
|
)
|
|
|
|
errDNSLabelLength = fmt.Errorf(
|
|
"%w: DNS labels must be between 1 and 63 characters in length", errBase,
|
|
)
|
|
)
|
|
|
|
// Base64 generates a random base64 encoded string of n number of bytes.
|
|
//
|
|
// Length of the returned string is about one third greater than the value of n,
|
|
// and it may contain characters A-Z, a-z, 0-9, "+", "/", and "=".
|
|
func Base64(n int) (string, error) {
|
|
b, err := Bytes(n)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return base64.StdEncoding.EncodeToString(b), nil
|
|
}
|
|
|
|
// Base64URL generates a URL-safe un-padded random base64 encoded string of n
|
|
// number of bytes.
|
|
//
|
|
// Length of the returned string is about one third greater than the value of n,
|
|
// and it may contain characters A-Z, a-z, 0-9, "-", and "_".
|
|
func Base64URL(n int) (string, error) {
|
|
b, err := Bytes(n)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
|
}
|
|
|
|
// Hex generates a random hexadecimal encoded string of n number of bytes.
|
|
//
|
|
// Length of the returned string is twice the value of n, and it may contain
|
|
// characters 0-9 and a-f.
|
|
func Hex(n int) (string, error) {
|
|
b, err := Bytes(n)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(b), nil
|
|
}
|
|
|
|
// Alphanumeric generates a random alphanumeric string of n length.
|
|
//
|
|
// The returned string may contain A-Z, a-z, and 0-9.
|
|
func Alphanumeric(n int) (string, error) {
|
|
return String(n, alphanumericChars)
|
|
}
|
|
|
|
// Alphabetic generates a random alphabetic string of n length.
|
|
//
|
|
// The returned string may contain A-Z, and a-z.
|
|
func Alphabetic(n int) (string, error) {
|
|
return String(n, alphabeticChars)
|
|
}
|
|
|
|
// Numeric generates a random numeric string of n length.
|
|
//
|
|
// The returned string may contain 0-9.
|
|
func Numeric(n int) (string, error) {
|
|
return String(n, numericChars)
|
|
}
|
|
|
|
// Upper generates a random uppercase alphabetic string of n length.
|
|
//
|
|
// The returned string may contain A-Z.
|
|
func Upper(n int) (string, error) {
|
|
return String(n, upperChars)
|
|
}
|
|
|
|
// UpperNumeric generates a random uppercase alphanumeric string of n length.
|
|
//
|
|
// The returned string may contain A-Z and 0-9.
|
|
func UpperNumeric(n int) (string, error) {
|
|
return String(n, upperNumericChars)
|
|
}
|
|
|
|
// Lower generates a random lowercase alphabetic string of n length.
|
|
//
|
|
// The returned string may contain a-z.
|
|
func Lower(n int) (string, error) {
|
|
return String(n, lowerChars)
|
|
}
|
|
|
|
// LowerNumeric generates a random lowercase alphanumeric string of n length.
|
|
//
|
|
// The returned string may contain A-Z and 0-9.
|
|
func LowerNumeric(n int) (string, error) {
|
|
return String(n, lowerNumericChars)
|
|
}
|
|
|
|
// String generates a random string of n length using the given ASCII alphabet.
|
|
//
|
|
// The specified alphabet determines what characters are used in the returned
|
|
// random string. The alphabet can only contain ASCII characters, use
|
|
// UnicodeString() if you need a alphabet with Unicode characters.
|
|
func String(n int, alphabet string) (string, error) {
|
|
if !isASCII(alphabet) {
|
|
return "", errNonASCIIAlphabet
|
|
}
|
|
|
|
l := big.NewInt(int64(len(alphabet)))
|
|
b := make([]byte, n)
|
|
for i := 0; i < n; i++ {
|
|
index, err := rand.Int(rand.Reader, l)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
b[i] = alphabet[index.Int64()]
|
|
}
|
|
|
|
return string(b), nil
|
|
}
|
|
|
|
// UnicodeString generates a random string of n length using the given Unicode
|
|
// alphabet.
|
|
//
|
|
// The specified alphabet determines what characters are used in the returned
|
|
// random string. The length of the returned string will be n or greater
|
|
// depending on the byte-length of characters which were randomly selected from
|
|
// the alphabet.
|
|
func UnicodeString(n int, alphabet []rune) (string, error) {
|
|
l := big.NewInt(int64(len(alphabet)))
|
|
b := make([]rune, n)
|
|
for i := 0; i < n; i++ {
|
|
index, err := rand.Int(rand.Reader, l)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
b[i] = alphabet[index.Int64()]
|
|
}
|
|
|
|
return string(b), nil
|
|
}
|
|
|
|
// DNSLabel returns a random string of n length in a DNS label compliant format
|
|
// as defined in RFC 1035, section 2.3.1, Preferred name syntax:
|
|
// https://tools.ietf.org/html/rfc1035#section-2.3.1
|
|
//
|
|
// It also adheres to RFC 5891, section 4.2.3.1, Hyphen Restrictions:
|
|
// https://tools.ietf.org/html/rfc5891#section-4.2.3.1
|
|
//
|
|
// In summary, the generated random string will:
|
|
//
|
|
// - be between 1 and 63 characters in length, other n values returns a error
|
|
// - first character will be one of a-z
|
|
// - last character will be one of a-z or 0-9
|
|
// - in-between first and last characters consist of a-z, 0-9, or "-"
|
|
// - potentially contain two or more consecutive "-", except the 3rd and 4th
|
|
// characters, as that would violate RFC 5891.
|
|
func DNSLabel(n int) (string, error) {
|
|
switch {
|
|
case n < 1 || n > 63:
|
|
return "", errDNSLabelLength
|
|
case n == 1:
|
|
return String(1, lowerChars)
|
|
default:
|
|
// First character of a DNS label allows only a-z characters.
|
|
head, err := String(1, lowerChars)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Last character of a DNS label allows only a-z and 0-9 characters.
|
|
tail, err := String(1, lowerNumericChars)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if n < 3 {
|
|
return head + tail, nil
|
|
}
|
|
|
|
// The middle of a DNS label allows only a-z, 0-9, and "-" characters.
|
|
bodyLen := n - 2
|
|
body := make([]byte, bodyLen)
|
|
var last byte
|
|
var l *big.Int
|
|
|
|
for i := 0; i < bodyLen; i++ {
|
|
// Prevent two consecutive hyphens characters in positions 3 and 4,
|
|
// in accordance RFC 5891, section 4.2.3.1, Hyphen Restrictions:
|
|
// https://tools.ietf.org/html/rfc5891#section-4.2.3.1
|
|
if i == 2 && last == byte(45) {
|
|
l = big.NewInt(int64(len(lowerNumericChars)))
|
|
} else {
|
|
l = big.NewInt(int64(len(dnsLabelChars)))
|
|
}
|
|
|
|
index, err := rand.Int(rand.Reader, l)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if i == 2 && last == byte(45) {
|
|
last = lowerNumericChars[index.Int64()]
|
|
} else {
|
|
last = dnsLabelChars[index.Int64()]
|
|
}
|
|
|
|
body[i] = last
|
|
}
|
|
|
|
return head + string(body) + tail, nil
|
|
}
|
|
}
|
|
|
|
func isASCII(s string) bool {
|
|
for _, c := range s {
|
|
if c > unicode.MaxASCII {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|