From 5fc3e13745d1a706720995b68747cdf389da8cbd Mon Sep 17 00:00:00 2001 From: hazelmayank Date: Sun, 10 May 2026 17:57:50 +0530 Subject: [PATCH] fix(rand): return errors for invalid random string inputs Signed-off-by: hazelmayank --- pkg/util/rand/rand.go | 9 +++++ pkg/util/rand/rand_test.go | 83 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 pkg/util/rand/rand_test.go diff --git a/pkg/util/rand/rand.go b/pkg/util/rand/rand.go index 1e748bf..1162da9 100644 --- a/pkg/util/rand/rand.go +++ b/pkg/util/rand/rand.go @@ -14,7 +14,16 @@ func String(n int) (string, error) { } // StringFromCharset generates, from a given charset, a cryptographically-secure pseudo-random string of a given length. +// +// Returns an error when n is negative, or when n > 0 and charset is empty. +// n == 0 returns "" with no error regardless of the charset. func StringFromCharset(n int, charset string) (string, error) { + if n < 0 { + return "", fmt.Errorf("rand: requested length %d is negative", n) + } + if n > 0 && len(charset) == 0 { + return "", fmt.Errorf("rand: cannot generate %d-character string from empty charset", n) + } b := make([]byte, n) maxIdx := big.NewInt(int64(len(charset))) for i := 0; i < n; i++ { diff --git a/pkg/util/rand/rand_test.go b/pkg/util/rand/rand_test.go new file mode 100644 index 0000000..d6d1fef --- /dev/null +++ b/pkg/util/rand/rand_test.go @@ -0,0 +1,83 @@ +package rand + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestString_ValidLengths(t *testing.T) { + tests := []struct { + name string + n int + }{ + {"zero length", 0}, + {"single char", 1}, + {"medium length", 24}, + {"PKCE-style length", 43}, + {"long", 128}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := String(tt.n) + require.NoError(t, err) + assert.Len(t, got, tt.n) + for i := 0; i < len(got); i++ { + assert.True(t, strings.IndexByte(letterBytes, got[i]) >= 0, + "byte %q at position %d not in default letter charset", got[i], i) + } + }) + } +} + +func TestString_NegativeLengthReturnsError(t *testing.T) { + got, err := String(-1) + require.Error(t, err) + assert.Equal(t, "", got) +} + +func TestStringFromCharset_ValidInputs(t *testing.T) { + tests := []struct { + name string + n int + charset string + }{ + {"empty length, empty charset", 0, ""}, + {"empty length, non-empty charset", 0, "abc"}, + {"single byte from single-byte charset", 1, "x"}, + {"PKCE verifier charset", 43, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := StringFromCharset(tt.n, tt.charset) + require.NoError(t, err) + assert.Len(t, got, tt.n) + for i := 0; i < len(got); i++ { + assert.True(t, strings.IndexByte(tt.charset, got[i]) >= 0, + "byte %q at position %d not in charset %q", got[i], i, tt.charset) + } + }) + } +} + +func TestStringFromCharset_InvalidInputsReturnError(t *testing.T) { + tests := []struct { + name string + n int + charset string + }{ + {"negative length, non-empty charset", -1, "abc"}, + {"negative length, empty charset", -1, ""}, + {"positive length, empty charset", 1, ""}, + {"large positive length, empty charset", 100, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := StringFromCharset(tt.n, tt.charset) + require.Error(t, err) + assert.Equal(t, "", got) + }) + } +}