blob: 6d4365db3474a1123f714dae24750b278ae52497 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package util
import (
"fmt"
"regexp"
"strings"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/security"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
"v.io/v23/verror"
)
// Encode escapes a component name for use in a Syncbase object name. In
// particular, it replaces bytes "%" and "/" with the "%" character followed by
// the byte's two-digit hex code. Clients using the client library need not
// escape names themselves; the client library does so on their behalf.
func Encode(s string) string {
return naming.EncodeAsNameElement(s)
}
// Decode is the inverse of Encode.
func Decode(s string) (string, error) {
res, ok := naming.DecodeFromNameElement(s)
if !ok {
return "", fmt.Errorf("failed to decode component name: %s", s)
}
return res, nil
}
// EncodeId encodes the given Id for use in a Syncbase object name.
func EncodeId(id wire.Id) string {
// Note that "," is not allowed to appear in blessing patterns. We also
// could've used "/" as a separator, but then we would've had to be more
// careful with decoding and splitting name components elsewhere.
// TODO(sadovsky): Maybe define "," constant in v23/services/syncbase.
return Encode(id.Blessing + "," + id.Name)
}
// DecodeId is the inverse of EncodeId.
func DecodeId(s string) (wire.Id, error) {
dec, err := Decode(s)
if err != nil {
return wire.Id{}, err
}
parts := strings.SplitN(dec, ",", 2)
if len(parts) != 2 {
return wire.Id{}, fmt.Errorf("failed to decode id: %s", s)
}
return wire.Id{Blessing: parts[0], Name: parts[1]}, nil
}
// Currently we use \xff for perms index storage and \xfe as a component
// separator in storage engine keys. \xfc and \xfd are not used. In the future
// we might use \xfc followed by "c", "d", "e", or "f" as an order-preserving
// 2-byte encoding of our reserved bytes. Note that all valid UTF-8 byte
// sequences are allowed.
var reservedBytes = []string{"\xfc", "\xfd", "\xfe", "\xff"}
func containsAnyOf(s string, needles []string) bool {
for _, v := range needles {
if strings.Contains(s, v) {
return true
}
}
return false
}
// TODO(ivanpi): Consider relaxing this.
var idNameRegexp *regexp.Regexp = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_]*$")
const (
// maxBlessingLen is the max allowed number of bytes in an Id blessing.
// TODO(ivanpi): Blessings are theoretically unbounded in length.
maxBlessingLen = 1024
// maxNameLen is the max allowed number of bytes in an Id name.
maxNameLen = 64
)
// ValidateId returns nil iff the given Id is a valid database, collection, or
// syncgroup Id.
func ValidateId(id wire.Id) error {
if x := len([]byte(id.Blessing)); x == 0 {
return fmt.Errorf("Id blessing cannot be empty")
} else if x > maxBlessingLen {
return fmt.Errorf("Id blessing %q exceeds %d bytes", id.Blessing, maxBlessingLen)
}
if x := len([]byte(id.Name)); x == 0 {
return fmt.Errorf("Id name cannot be empty")
} else if x > maxNameLen {
return fmt.Errorf("Id name %q exceeds %d bytes", id.Name, maxNameLen)
}
if !security.BlessingPattern(id.Blessing).IsValid() {
return fmt.Errorf("Id blessing %q is not a valid blessing pattern", id.Blessing)
}
if containsAnyOf(id.Blessing, reservedBytes) {
return fmt.Errorf("Id blessing %q contains a reserved byte (one of %q)", id.Blessing, reservedBytes)
}
if !idNameRegexp.MatchString(id.Name) {
return fmt.Errorf("Id name %q does not satisfy regex %q", id.Name, idNameRegexp.String())
}
return nil
}
// ValidateRowKey returns nil iff the given string is a valid row key.
func ValidateRowKey(s string) error {
if s == "" {
return fmt.Errorf("row key cannot be empty")
}
if containsAnyOf(s, reservedBytes) {
return fmt.Errorf("row key %q contains a reserved byte (one of %q)", s, reservedBytes)
}
return nil
}
// ParseCollectionRowPair splits the "<collectionId>/<row>" part of a Syncbase
// object name into the collection id and the row key or prefix.
func ParseCollectionRowPair(ctx *context.T, pattern string) (wire.Id, string, error) {
parts := strings.SplitN(pattern, "/", 2)
if len(parts) != 2 { // require both collection and row parts
return wire.Id{}, "", verror.New(verror.ErrBadArg, ctx, pattern)
}
collectionEnc, row := parts[0], parts[1]
collection, err := DecodeId(parts[0])
if err == nil {
err = ValidateId(collection)
}
if err != nil {
return wire.Id{}, "", verror.New(wire.ErrInvalidName, ctx, collectionEnc, err)
}
if row != "" {
if err := ValidateRowKey(row); err != nil {
return wire.Id{}, "", verror.New(wire.ErrInvalidName, ctx, row, err)
}
}
return collection, row, nil
}
// PrefixRangeStart returns the start of the row range for the given prefix.
func PrefixRangeStart(p string) string {
return p
}
// PrefixRangeLimit returns the limit of the row range for the given prefix.
func PrefixRangeLimit(p string) string {
// A string is a []byte, i.e. can be thought of as a base-256 number. The code
// below effectively adds 1 to this number, then chops off any trailing \x00
// bytes. If the input string consists entirely of \xff bytes, we return an
// empty string.
x := []byte(p)
for len(x) > 0 {
if x[len(x)-1] == 255 {
x = x[:len(x)-1] // chop off trailing \x00
} else {
x[len(x)-1] += 1 // add 1
break // no carry
}
}
return string(x)
}
// IsPrefix returns true if start and limit together represent a prefix range.
// If true, start is the represented prefix.
func IsPrefix(start, limit string) bool {
return PrefixRangeLimit(start) == limit
}
// AccessController provides access control for various syncbase objects.
type AccessController interface {
// SetPermissions replaces the current Permissions for an object.
// For detailed documentation, see Object.SetPermissions.
SetPermissions(ctx *context.T, perms access.Permissions, version string) error
// GetPermissions returns the current Permissions for an object.
// For detailed documentation, see Object.GetPermissions.
GetPermissions(ctx *context.T) (perms access.Permissions, version string, err error)
}
// AppBlessingFromContext returns an app blessing pattern from the given
// context.
// TODO(sadovsky,ashankar): Implement.
func AppBlessingFromContext(ctx *context.T) (string, error) {
// NOTE(sadovsky): For now, we use a blessing string that will be easy to
// find-replace when we actually implement this method.
return "v.io:a:xyz", nil
}
// UserBlessingFromContext returns a user blessing pattern from the given
// context.
// TODO(sadovsky,ashankar): Implement.
func UserBlessingFromContext(ctx *context.T) (string, error) {
// NOTE(sadovsky): For now, we use a blessing string that will be easy to
// find-replace when we actually implement this method.
return "v.io:u:sam", nil
}