Skip to content
9 changes: 6 additions & 3 deletions byteseq.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,13 @@ func TrimRight[S byteSeq](s S, cutset byte) S {
// This is an optimized version that's faster than strings/bytes.TrimSpace for ASCII strings.
// It removes the following ASCII whitespace characters: space, tab, newline, carriage return, vertical tab, and form feed.
func TrimSpace[S byteSeq](s S) S {
i, j := 0, len(s)-1
n := len(s)
if n == 0 {
return s
}

// fast path for empty string
if j < 0 {
i, j := 0, n-1
if !whitespaceTable[s[i]] && !whitespaceTable[s[j]] {
return s
}

Expand Down
71 changes: 57 additions & 14 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,20 @@ import (
"github.com/google/uuid"
)

// randRead is a package-level indirection for crypto/rand.Read so tests
// can override it to simulate failures.
var randRead = rand.Read
const (
defaultSecureTokenLength = 32
maxFastTokenEncodedLength = 43 // base64.RawURLEncoding.EncodedLen(32)
)

func readRandomOrPanic(dst []byte) {
if _, err := rand.Read(dst); err != nil {
// On supported Go versions (1.24+), crypto/rand.Read panics internally and
// does not return errors. This check preserves explicit panic semantics if
// the behavior changes or an alternate implementation is used in the future.
// See: https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/crypto/rand/rand.go
panic(fmt.Errorf("utils: failed to read random bytes for token: %w", err))
}
}

const (
toLowerTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
Expand Down Expand Up @@ -60,25 +71,29 @@ func UUID() string {
// Panics if the random source fails.
func GenerateSecureToken(length int) string {
if length <= 0 {
length = 32
length = defaultSecureTokenLength
}
bytes := make([]byte, length)
if _, err := randRead(bytes); err != nil {
// On Go 1.24+, crypto/rand.Read panics internally and never returns an error.
// On Go 1.23 and earlier, we panic for the same reasons: RNG failures indicate
// a broken system state (uninitialized entropy pool, misconfigured VM, etc.)
// that is almost certainly permanent rather than transient.
// See: https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/crypto/rand/rand.go
// https://go.dev/issue/66821
panic(fmt.Errorf("utils: failed to read random bytes for token: %w", err))

if length == defaultSecureTokenLength {
var randomBuf [defaultSecureTokenLength]byte
src := randomBuf[:]
readRandomOrPanic(src)

var encoded [maxFastTokenEncodedLength]byte
encodedLen := base64.RawURLEncoding.EncodedLen(length)
base64.RawURLEncoding.Encode(encoded[:encodedLen], src)
return string(encoded[:encodedLen])
}

bytes := make([]byte, length)
readRandomOrPanic(bytes)
return base64.RawURLEncoding.EncodeToString(bytes)
}

// SecureToken generates a secure token with 32 bytes of entropy.
// Panics if the random source fails. See GenerateSecureToken for details.
func SecureToken() string {
return GenerateSecureToken(32)
return GenerateSecureToken(defaultSecureTokenLength)
}

// FunctionName returns function name
Expand Down Expand Up @@ -124,6 +139,34 @@ func ConvertToBytes(humanReadableString string) int {
return 0
}

// Fast path for plain byte values (e.g. "42", "42B", "42b").
var sizeFast uint64
maxInt := uint64(math.MaxInt)
i := 0
for ; i < strLen; i++ {
c := humanReadableString[i]
if c < '0' || c > '9' {
break
}
d := uint64(c - '0')
if sizeFast > maxInt/10 || (sizeFast == maxInt/10 && d > maxInt%10) {
sizeFast = maxInt
} else if sizeFast < maxInt {
sizeFast = sizeFast*10 + d
}
}
if i > 0 {
if i == strLen {
return int(sizeFast)
}
if i+1 == strLen {
last := humanReadableString[i]
if last == 'b' || last == 'B' {
return int(sizeFast)
}
}
}

// Find the last digit position by scanning backwards
// Also identify the unit prefix position in the same pass
lastNumberPos := -1
Expand Down
15 changes: 0 additions & 15 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package utils

import (
"errors"
"net"
"os"
"testing"
Expand Down Expand Up @@ -116,20 +115,6 @@ func Test_GenerateSecureToken_Concurrency(t *testing.T) {
require.Len(t, results, iterations)
}

func Test_GenerateSecureToken_ErrorOnRandFail(t *testing.T) {
// Save and restore original randRead
orig := randRead
defer func() { randRead = orig }()

// Simulate read failure
randRead = func(_ []byte) (int, error) {
return 0, errors.New("simulated failure")
}

// Should panic on failure
require.Panics(t, func() { GenerateSecureToken(16) })
}

func Test_SecureToken(t *testing.T) {
t.Parallel()
token := SecureToken()
Expand Down
46 changes: 27 additions & 19 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,31 @@ var smallInts [100]string
// smallNegInts contains precomputed string representations for small negative integers -1 to -99
var smallNegInts [100]string

// uint8Strs contains precomputed string representations for all uint8 values.
var uint8Strs [256]string

// int8Strs contains precomputed string representations for all int8 values indexed by uint8(value).
var int8Strs [256]string

func init() {
for i := range 100 {
smallInts[i] = formatUintSmall(uint64(i))
if i > 0 {
smallNegInts[i] = "-" + smallInts[i]
}
}

for i := range 256 {
v := uint8(i)
uint8Strs[i] = formatUint8Slow(v)

sv := int8(i)
if sv >= 0 {
int8Strs[i] = uint8Strs[sv]
} else {
int8Strs[i] = "-" + uint8Strs[uint8(-sv)]
}
}
}

func formatUintSmall(n uint64) string {
Expand All @@ -22,6 +40,13 @@ func formatUintSmall(n uint64) string {
return string([]byte{byte(n/10) + '0', byte(n%10) + '0'})
}

func formatUint8Slow(n uint8) string {
if n < 100 {
return smallInts[n]
}
return string([]byte{n/100 + '0', (n/10)%10 + '0', n%10 + '0'})
}

// formatUintBuf writes the digits of n into buf from the end and returns the start index.
// buf must be at least 20 bytes.
func formatUintBuf(buf *[20]byte, n uint64) int {
Expand Down Expand Up @@ -156,29 +181,12 @@ func FormatInt16(n int16) string {

// FormatUint8 formats a uint8 as a decimal string.
func FormatUint8(n uint8) string {
if n < 100 {
return smallInts[n]
}
// uint8 max is 255, so max 3 digits
return string([]byte{n/100 + '0', (n/10)%10 + '0', n%10 + '0'})
return uint8Strs[n]
}

// FormatInt8 formats an int8 as a decimal string.
func FormatInt8(n int8) string {
if n >= 0 && n < 100 {
return smallInts[n]
}
if n < 0 && n > -100 {
return smallNegInts[-n]
}
// Only -128 to -100 and 100 to 127 reach here
if n >= 0 {
un := uint8(n)
return string([]byte{un/100 + '0', (un/10)%10 + '0', un%10 + '0'})
}
// n is -128 to -100
un := uint8(-n)
return string([]byte{'-', un/100 + '0', (un/10)%10 + '0', un%10 + '0'})
return int8Strs[uint8(n)]
}

// AppendUint appends the decimal string representation of n to dst.
Expand Down
17 changes: 17 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import (
)

const MIMEOctetStream = "application/octet-stream"
const (
contentTypeApplicationJSON = "application/json"
contentTypeApplicationXML = "application/xml"
contentTypeApplicationFormURLEncoded = "application/x-www-form-urlencoded"
contentTypePrefixApplicationWithSlashLen = len("application/")
)

// GetMIME returns the content-type of a file extension
func GetMIME(extension string) string {
Expand Down Expand Up @@ -74,6 +80,17 @@ func ParseVendorSpecificContentType(cType string, caseInsensitive ...bool) strin
return cType
}

if slashIndex+1 == contentTypePrefixApplicationWithSlashLen {
switch parsableType {
case "json":
return contentTypeApplicationJSON
case "xml":
return contentTypeApplicationXML
case "x-www-form-urlencoded":
return contentTypeApplicationFormURLEncoded
}
}

return working[:slashIndex+1] + parsableType
}

Expand Down
Loading
Loading