blob: d79308823c5e7ee4f9501121a2ac5bc2630ed306 [file] [log] [blame]
// Copyright 2016 The Upspin 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 valid does validation of various data types.
// For the most part, its functions are used by servers and enforce
// stronger constraints than client code needs to follow.
package valid // import "upspin.io/valid"
import (
"strconv"
"upspin.io/access"
"upspin.io/errors"
"upspin.io/path"
"upspin.io/upspin"
"upspin.io/user"
)
// UserName verifies that the name is a syntactically valid user's email address.
// It also requires that the domain name be lower-cased to avoid ambiguity.
func UserName(userName upspin.UserName) error {
const op errors.Op = "valid.UserName"
u, _, d, err := user.Parse(userName)
if err != nil {
return errors.E(op, err)
}
if string(userName) != u+"@"+d {
return errors.E(op, errors.Invalid, userName, "not canonically formatted")
}
if userName == access.AllUsers {
return errors.E(op, errors.Invalid, userName, "reserved user name")
}
return nil
}
// User verifies that the User struct is valid, that is, that all its fields are syntactically valid.
func User(user *upspin.User) error {
const op errors.Op = "valid.User"
if err := UserName(user.Name); err != nil {
return errors.E(op, errors.Invalid, err)
}
for _, ep := range user.Dirs {
if err := Endpoint(ep); err != nil {
return errors.E(op, errors.Invalid, err)
}
}
for _, ep := range user.Stores {
if err := Endpoint(ep); err != nil {
return errors.E(op, errors.Invalid, err)
}
}
// TODO: Check public key?
return nil
}
// validPathName verifies that the name is valid, clean and canonically formatted.
// See upspin.io/path.Clean for the specification. One important check is that this
// function requires a user's root to have the trailing slash; path.Parse does not.
func validPathName(name upspin.PathName) error {
parsed, err := path.Parse(name)
if err != nil {
return err
}
if parsed.Path() != name {
return errors.Str("name is not clean")
}
return nil
}
// DirBlock verifies that the block is valid, that is, that it has a
// greater-than-zero Size, non-negative Offset, and valid Location.
func DirBlock(block upspin.DirBlock) error {
const op errors.Op = "valid.DirBlock"
if block.Size <= 0 {
return errors.E(op, errors.Invalid, errors.Errorf("non-positive block size %d", block.Size))
}
if block.Offset < 0 {
return errors.E(op, errors.Invalid, errors.Errorf("negative block offset %d", block.Offset))
}
if err := Endpoint(block.Location.Endpoint); err != nil {
return errors.E(op, err)
}
if err := Reference(block.Location.Reference); err != nil {
return errors.E(op, err)
}
return nil
}
// Endpoint verifies that the endpoint looks syntactically valid. It does not check that the
// endpoint defines a reachable server.
func Endpoint(endpoint upspin.Endpoint) error {
const op errors.Op = "valid.Endpoint"
switch endpoint.Transport {
case upspin.InProcess:
// OK if there is a netaddr, or not.
case upspin.Unassigned:
if endpoint.NetAddr != "" {
return errors.E(op, errors.Invalid, errors.Errorf("%q: extraneous network address", endpoint))
}
case upspin.Remote:
if endpoint.NetAddr == "" {
return errors.E(op, errors.Invalid, errors.Errorf("%q: missing network address", endpoint))
}
default:
return errors.E(op, errors.Invalid, errors.Errorf("%d unrecognized transport", endpoint.Transport))
}
return nil
}
// DirEntry verifies that the DirEntry is valid. It is intended for use by DirServer.Put,
// and so constrains the entry more rigorously than, for instance, the DirEntries that
// may be returned from servers. For example, it requires that a directory has no blocks,
// but a DirEntry for a directory returned by a server may contain block data.
// Rules:
// - present and valid Name and SignedName
// - Name is equal to SignedName
// - blocks may be present only if Attr == AttrNone
// - Link may be present only if Attr == AttrLink
// - Attr must not include AttrIncomplete
// - Packing must be known
// - Sequence must have a known special value or be non-negative
// - for non-directory entries, a Writer field is required.
func DirEntry(entry *upspin.DirEntry) error {
const op errors.Op = "valid.DirEntry"
// SignedName must be good.
if err := validPathName(entry.SignedName); err != nil {
return errors.E(op, errors.Invalid, entry.SignedName, err)
}
// Name must match.
if entry.Name != entry.SignedName {
return errors.E(op, errors.Invalid, entry.Name, "Name and SignedName must match")
}
// Is the entry incomplete? Servers must not accept such entries.
// (Although they may return them.)
if entry.IsIncomplete() {
return errors.E(op, errors.Invalid, entry.Name, "entry must not be incomplete")
}
// Attribute must be valid and consistent with entry.
switch entry.Attr {
case upspin.AttrNone, upspin.AttrDirectory:
// OK
case upspin.AttrLink:
if err := validPathName(entry.Link); err != nil {
return errors.E(op, errors.Invalid, entry.Name, err)
}
default:
return errors.E(op, errors.Invalid, entry.Name, errors.Errorf("invalid file attribute %d", entry.Attr))
}
// Blocks only for AttrNone
if entry.Attr != upspin.AttrNone && len(entry.Blocks) > 0 {
return errors.E(op, errors.Invalid, entry.Name, "link or directory cannot have data")
}
// Link only for AttrLink
if entry.Attr != upspin.AttrLink && entry.Link != "" {
return errors.E(op, errors.Invalid, entry.Name, "only links can have Link set")
}
// Packing must be valid.
switch entry.Packing {
case upspin.PlainPack, upspin.EEPack, upspin.EEIntegrityPack:
// OK
case upspin.UnassignedPack:
if entry.IsDir() {
// Okay for directory; DirServer chooses.
break
}
fallthrough
default:
return errors.E(op, errors.Invalid, entry.Name, errors.Errorf("invalid packing %d", entry.Packing))
}
// Sequence must be valid.
if entry.Sequence < 0 && entry.Sequence != upspin.SeqNotExist {
return errors.E(op, errors.Invalid, entry.Name, errors.Errorf("negative sequence number %d", entry.Sequence))
}
// There must be no holes or overlaps in blocks and blocks must be valid.
offset := int64(0)
for _, block := range entry.Blocks {
if block.Offset != offset {
return errors.E(op, errors.Invalid, entry.Name, "data blocks are not contiguous")
}
offset += block.Size
if err := DirBlock(block); err != nil {
return errors.E(op, errors.Invalid, entry.Name, err)
}
}
// For non-directory entries, a Writer field is required.
if entry.IsDir() {
return nil
}
if err := UserName(entry.Writer); err != nil {
return errors.E(op, "invalid writer", err)
}
return nil
}
// Reference verifies that the Reference is valid. A Reference must be a non-empty
// UTF-8-encoded string of printable characters, as defined by Unicode. Also, although
// printable, the replacement rune (U+FFFD) is considered invalid, even if it is explicitly
// present, as it usually indicates erroneous UTF-8 or Unicode.
func Reference(ref upspin.Reference) error {
const op errors.Op = "valid.Reference"
if ref == "" {
return errors.E(op, errors.Invalid, "empty reference")
}
previ := 0
for i, r := range ref {
// U+FFFD might mean invalid UTF-8, or be present for real. Either way, we reject it.
if r == '\uFFFD' {
if i-previ == 1 {
return errors.E(op, errors.Invalid, errors.Errorf("invalid UTF-8 in reference"))
}
return errors.E(op, errors.Invalid, errors.Errorf("invalid code point %#U in reference", r))
}
if !strconv.IsPrint(r) {
return errors.E(op, errors.Invalid, errors.Errorf("invalid code point %#U in reference", r))
}
previ = i
}
return nil
}