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}$`) // reserved names would shadow a route or a well-known file if they were ever // allowed into the object namespace. var reserved = map[string]bool{ "d": true, "i": true, "api": true, "static": true, "admin": true, "login": true, "logout": true, "upload": true, "favicon.ico": true, "robots.txt": true, "index.html": true, "sitemap.xml": true, "tokens.json": true, "objects": true, } 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. 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 } if reserved[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 }