70 lines
2.2 KiB
Go
70 lines
2.2 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}$`)
|
|
|
|
// 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
|
|
}
|