Files
2026-09-13 10:28:33 +02:00

64 lines
2.1 KiB
Go

package store
import (
"crypto/rand"
"encoding/hex"
"errors"
"regexp"
"strings"
)
// vanityRe is deliberately narrow: lowercase alphanumerics plus dot, dash and
// underscore, starting with an alphanumeric, 2-64 characters. Anything that
// could be mistaken for a path element, a dotfile or a traversal is excluded.
var vanityRe = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{1,63}$`)
var ErrBadID = errors.New("invalid name")
// CleanID validates an id arriving from a URL or from a vanity request and
// returns its canonical form. IDs are lowercased so that a case-insensitive
// filesystem cannot be tricked into treating two distinct names as one object.
//
// This is the *only* function permitted to turn caller input into a path
// element; every filesystem path in this package is built from its output.
//
// There is deliberately no list of reserved words. An id appears only under
// /d/ and /i/ in a URL, and only as a directory of its own inside the objects
// directory on disk, so no spelling of it can shadow a route or a file of
// ours: "favicon.png" and "admin" are ordinary names and refusing them would
// be theatre.
func CleanID(s string) (string, error) {
s = strings.ToLower(strings.TrimSpace(s))
if !vanityRe.MatchString(s) {
return "", ErrBadID
}
// The regexp permits interior dots; a doubled dot or a trailing dot is
// still refused so no spelling of a traversal survives.
if strings.Contains(s, "..") || strings.HasSuffix(s, ".") {
return "", ErrBadID
}
return s, nil
}
// NewUUID returns a random RFC 4122 version 4 UUID.
func NewUUID() (string, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return "", err
}
b[6] = (b[6] & 0x0f) | 0x40 // version 4
b[8] = (b[8] & 0x3f) | 0x80 // variant 10
h := hex.EncodeToString(b[:])
return h[:8] + "-" + h[8:12] + "-" + h[12:16] + "-" + h[16:20] + "-" + h[20:], nil
}
// NewSecret returns a high-entropy URL-safe secret, used for both API tokens
// and per-object delete tokens.
func NewSecret() (string, error) {
var b [32]byte
if _, err := rand.Read(b[:]); err != nil {
return "", err
}
return hex.EncodeToString(b[:]), nil
}