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 }