github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/schema/schema.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package schema manipulates Camlistore schema blobs.
    18  //
    19  // A schema blob is a JSON-encoded blob that describes other blobs.
    20  // See documentation in Camlistore's doc/schema/ directory.
    21  package schema
    22  
    23  import (
    24  	"bytes"
    25  	"crypto/rand"
    26  	"crypto/sha1"
    27  	"encoding/base64"
    28  	"encoding/json"
    29  	"errors"
    30  	"fmt"
    31  	"hash"
    32  	"io"
    33  	"os"
    34  	"reflect"
    35  	"strconv"
    36  	"strings"
    37  	"sync"
    38  	"time"
    39  
    40  	"camlistore.org/pkg/blob"
    41  	"camlistore.org/pkg/types"
    42  	"camlistore.org/third_party/github.com/camlistore/goexif/exif"
    43  )
    44  
    45  // MaxSchemaBlobSize represents the upper bound for how large
    46  // a schema blob may be.
    47  const MaxSchemaBlobSize = 1 << 20
    48  
    49  var sha1Type = reflect.TypeOf(sha1.New())
    50  
    51  var (
    52  	ErrNoCamliVersion = errors.New("schema: no camliVersion key in map")
    53  )
    54  
    55  var clockNow = time.Now
    56  
    57  type StatHasher interface {
    58  	Lstat(fileName string) (os.FileInfo, error)
    59  	Hash(fileName string) (blob.Ref, error)
    60  }
    61  
    62  // File is the interface returned when opening a DirectoryEntry that
    63  // is a regular file.
    64  type File interface {
    65  	io.Closer
    66  	io.ReaderAt
    67  	io.Reader
    68  	Size() int64
    69  }
    70  
    71  // Directory is a read-only interface to a "directory" schema blob.
    72  type Directory interface {
    73  	// Readdir reads the contents of the directory associated with dr
    74  	// and returns an array of up to n DirectoryEntries structures.
    75  	// Subsequent calls on the same file will yield further
    76  	// DirectoryEntries.
    77  	// If n > 0, Readdir returns at most n DirectoryEntry structures. In
    78  	// this case, if Readdir returns an empty slice, it will return
    79  	// a non-nil error explaining why. At the end of a directory,
    80  	// the error is os.EOF.
    81  	// If n <= 0, Readdir returns all the DirectoryEntries from the
    82  	// directory in a single slice. In this case, if Readdir succeeds
    83  	// (reads all the way to the end of the directory), it returns the
    84  	// slice and a nil os.Error. If it encounters an error before the
    85  	// end of the directory, Readdir returns the DirectoryEntry read
    86  	// until that point and a non-nil error.
    87  	Readdir(count int) ([]DirectoryEntry, error)
    88  }
    89  
    90  type Symlink interface {
    91  	// .. TODO
    92  }
    93  
    94  // DirectoryEntry is a read-only interface to an entry in a (static)
    95  // directory.
    96  type DirectoryEntry interface {
    97  	// CamliType returns the schema blob's "camliType" field.
    98  	// This may be "file", "directory", "symlink", or other more
    99  	// obscure types added in the future.
   100  	CamliType() string
   101  
   102  	FileName() string
   103  	BlobRef() blob.Ref
   104  
   105  	File() (File, error)           // if camliType is "file"
   106  	Directory() (Directory, error) // if camliType is "directory"
   107  	Symlink() (Symlink, error)     // if camliType is "symlink"
   108  }
   109  
   110  // dirEntry is the default implementation of DirectoryEntry
   111  type dirEntry struct {
   112  	ss      superset
   113  	fetcher blob.SeekFetcher
   114  	fr      *FileReader // or nil if not a file
   115  	dr      *DirReader  // or nil if not a directory
   116  }
   117  
   118  func (de *dirEntry) CamliType() string {
   119  	return de.ss.Type
   120  }
   121  
   122  func (de *dirEntry) FileName() string {
   123  	return de.ss.FileNameString()
   124  }
   125  
   126  func (de *dirEntry) BlobRef() blob.Ref {
   127  	return de.ss.BlobRef
   128  }
   129  
   130  func (de *dirEntry) File() (File, error) {
   131  	if de.fr == nil {
   132  		if de.ss.Type != "file" {
   133  			return nil, fmt.Errorf("DirectoryEntry is camliType %q, not %q", de.ss.Type, "file")
   134  		}
   135  		fr, err := NewFileReader(de.fetcher, de.ss.BlobRef)
   136  		if err != nil {
   137  			return nil, err
   138  		}
   139  		de.fr = fr
   140  	}
   141  	return de.fr, nil
   142  }
   143  
   144  func (de *dirEntry) Directory() (Directory, error) {
   145  	if de.dr == nil {
   146  		if de.ss.Type != "directory" {
   147  			return nil, fmt.Errorf("DirectoryEntry is camliType %q, not %q", de.ss.Type, "directory")
   148  		}
   149  		dr, err := NewDirReader(de.fetcher, de.ss.BlobRef)
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  		de.dr = dr
   154  	}
   155  	return de.dr, nil
   156  }
   157  
   158  func (de *dirEntry) Symlink() (Symlink, error) {
   159  	return 0, errors.New("TODO: Symlink not implemented")
   160  }
   161  
   162  // newDirectoryEntry takes a superset and returns a DirectoryEntry if
   163  // the Supserset is valid and represents an entry in a directory.  It
   164  // must by of type "file", "directory", or "symlink".
   165  // TODO: "fifo", "socket", "char", "block", probably.  later.
   166  func newDirectoryEntry(fetcher blob.SeekFetcher, ss *superset) (DirectoryEntry, error) {
   167  	if ss == nil {
   168  		return nil, errors.New("ss was nil")
   169  	}
   170  	if !ss.BlobRef.Valid() {
   171  		return nil, errors.New("ss.BlobRef was invalid")
   172  	}
   173  	switch ss.Type {
   174  	case "file", "directory", "symlink":
   175  		// Okay
   176  	default:
   177  		return nil, fmt.Errorf("invalid DirectoryEntry camliType of %q", ss.Type)
   178  	}
   179  	de := &dirEntry{ss: *ss, fetcher: fetcher} // defensive copy
   180  	return de, nil
   181  }
   182  
   183  // NewDirectoryEntryFromBlobRef takes a BlobRef and returns a
   184  // DirectoryEntry if the BlobRef contains a type "file", "directory"
   185  // or "symlink".
   186  // TODO: "fifo", "socket", "char", "block", probably.  later.
   187  func NewDirectoryEntryFromBlobRef(fetcher blob.SeekFetcher, blobRef blob.Ref) (DirectoryEntry, error) {
   188  	ss := new(superset)
   189  	err := ss.setFromBlobRef(fetcher, blobRef)
   190  	if err != nil {
   191  		return nil, fmt.Errorf("schema/filereader: can't fill superset: %v\n", err)
   192  	}
   193  	return newDirectoryEntry(fetcher, ss)
   194  }
   195  
   196  // superset represents the superset of common Camlistore JSON schema
   197  // keys as a convenient json.Unmarshal target.
   198  // TODO(bradfitz): unexport this type. Getting too gross. Move to schema.Blob
   199  type superset struct {
   200  	// BlobRef isn't for a particular metadata blob field, but included
   201  	// for convenience.
   202  	BlobRef blob.Ref
   203  
   204  	Version int    `json:"camliVersion"`
   205  	Type    string `json:"camliType"`
   206  
   207  	Signer blob.Ref `json:"camliSigner"`
   208  	Sig    string   `json:"camliSig"`
   209  
   210  	ClaimType string         `json:"claimType"`
   211  	ClaimDate types.Time3339 `json:"claimDate"`
   212  
   213  	Permanode blob.Ref `json:"permaNode"`
   214  	Attribute string   `json:"attribute"`
   215  	Value     string   `json:"value"`
   216  
   217  	// FileName and FileNameBytes represent one of the two
   218  	// representations of file names in schema blobs.  They should
   219  	// not be accessed directly.  Use the FileNameString accessor
   220  	// instead, which also sanitizes malicious values.
   221  	FileName      string        `json:"fileName"`
   222  	FileNameBytes []interface{} `json:"fileNameBytes"` // TODO: needs custom UnmarshalJSON?
   223  
   224  	SymlinkTarget      string        `json:"symlinkTarget"`
   225  	SymlinkTargetBytes []interface{} `json:"symlinkTargetBytes"` // TODO: needs custom UnmarshalJSON?
   226  
   227  	UnixPermission string `json:"unixPermission"`
   228  	UnixOwnerId    int    `json:"unixOwnerId"`
   229  	UnixOwner      string `json:"unixOwner"`
   230  	UnixGroupId    int    `json:"unixGroupId"`
   231  	UnixGroup      string `json:"unixGroup"`
   232  	UnixMtime      string `json:"unixMtime"`
   233  	UnixCtime      string `json:"unixCtime"`
   234  	UnixAtime      string `json:"unixAtime"`
   235  
   236  	// Parts are references to the data chunks of a regular file (or a "bytes" schema blob).
   237  	// See doc/schema/bytes.txt and doc/schema/files/file.txt.
   238  	Parts []*BytesPart `json:"parts"`
   239  
   240  	Entries blob.Ref   `json:"entries"` // for directories, a blobref to a static-set
   241  	Members []blob.Ref `json:"members"` // for static sets (for directory static-sets: blobrefs to child dirs/files)
   242  
   243  	// Target is a "share" blob's target (the thing being shared)
   244  	// Or it is the object being deleted in a DeleteClaim claim.
   245  	Target blob.Ref `json:"target"`
   246  	// Transitive is a property of a "share" blob.
   247  	Transitive bool `json:"transitive"`
   248  	// AuthType is a "share" blob's authentication type that is required.
   249  	// Currently (2013-01-02) just "haveref" (if you know the share's blobref,
   250  	// you get access: the secret URL model)
   251  	AuthType string         `json:"authType"`
   252  	Expires  types.Time3339 `json:"expires"` // or zero for no expiration
   253  }
   254  
   255  func parseSuperset(r io.Reader) (*superset, error) {
   256  	var ss superset
   257  	if err := json.NewDecoder(io.LimitReader(r, 1<<20)).Decode(&ss); err != nil {
   258  		return nil, err
   259  	}
   260  	return &ss, nil
   261  }
   262  
   263  // BlobReader returns a new Blob from the provided Reader r,
   264  // which should be the body of the provided blobref.
   265  // Note: the hash checksum is not verified.
   266  func BlobFromReader(ref blob.Ref, r io.Reader) (*Blob, error) {
   267  	if !ref.Valid() {
   268  		return nil, errors.New("schema.BlobFromReader: invalid blobref")
   269  	}
   270  	var buf bytes.Buffer
   271  	tee := io.TeeReader(r, &buf)
   272  	ss, err := parseSuperset(tee)
   273  	if err != nil {
   274  		return nil, err
   275  	}
   276  	var wb [16]byte
   277  	afterObj := 0
   278  	for {
   279  		n, err := tee.Read(wb[:])
   280  		afterObj += n
   281  		for i := 0; i < n; i++ {
   282  			if !isASCIIWhite(wb[i]) {
   283  				return nil, fmt.Errorf("invalid bytes after JSON schema blob in %v", ref)
   284  			}
   285  		}
   286  		if afterObj > MaxSchemaBlobSize {
   287  			break
   288  		}
   289  		if err == io.EOF {
   290  			break
   291  		}
   292  		if err != nil {
   293  			return nil, err
   294  		}
   295  	}
   296  	json := buf.String()
   297  	if len(json) > MaxSchemaBlobSize {
   298  		return nil, fmt.Errorf("schema: metadata blob %v is over expected limit; size=%d", ref, len(json))
   299  	}
   300  	return &Blob{ref, json, ss}, nil
   301  }
   302  
   303  func isASCIIWhite(b byte) bool {
   304  	switch b {
   305  	case ' ', '\t', '\r', '\n':
   306  		return true
   307  	}
   308  	return false
   309  }
   310  
   311  // BytesPart is the type representing one of the "parts" in a "file"
   312  // or "bytes" JSON schema.
   313  //
   314  // See doc/schema/bytes.txt and doc/schema/files/file.txt.
   315  type BytesPart struct {
   316  	// Size is the number of bytes that this part contributes to the overall segment.
   317  	Size uint64 `json:"size"`
   318  
   319  	// At most one of BlobRef or BytesRef must be non-zero
   320  	// (Valid), but it's illegal for both.
   321  	// If neither are set, this BytesPart represents Size zero bytes.
   322  	// BlobRef refers to raw bytes. BytesRef references a "bytes" schema blob.
   323  	BlobRef  blob.Ref `json:"blobRef,omitempty"`
   324  	BytesRef blob.Ref `json:"bytesRef,omitempty"`
   325  
   326  	// Offset optionally specifies the offset into BlobRef to skip
   327  	// when reading Size bytes.
   328  	Offset uint64 `json:"offset,omitempty"`
   329  }
   330  
   331  // stringFromMixedArray joins a slice of either strings or float64
   332  // values (as retrieved from JSON decoding) into a string.  These are
   333  // used for non-UTF8 filenames in "fileNameBytes" fields.  The strings
   334  // are UTF-8 segments and the float64s (actually uint8 values) are
   335  // byte values.
   336  func stringFromMixedArray(parts []interface{}) string {
   337  	var buf bytes.Buffer
   338  	for _, part := range parts {
   339  		if s, ok := part.(string); ok {
   340  			buf.WriteString(s)
   341  			continue
   342  		}
   343  		if num, ok := part.(float64); ok {
   344  			buf.WriteByte(byte(num))
   345  			continue
   346  		}
   347  	}
   348  	return buf.String()
   349  }
   350  
   351  func (ss *superset) SumPartsSize() (size uint64) {
   352  	for _, part := range ss.Parts {
   353  		size += uint64(part.Size)
   354  	}
   355  	return size
   356  }
   357  
   358  func (ss *superset) SymlinkTargetString() string {
   359  	if ss.SymlinkTarget != "" {
   360  		return ss.SymlinkTarget
   361  	}
   362  	return stringFromMixedArray(ss.SymlinkTargetBytes)
   363  }
   364  
   365  // FileNameString returns the schema blob's base filename.
   366  //
   367  // If the fileName field of the blob accidentally or maliciously
   368  // contains a slash, this function returns an empty string instead.
   369  func (ss *superset) FileNameString() string {
   370  	v := ss.FileName
   371  	if v == "" {
   372  		v = stringFromMixedArray(ss.FileNameBytes)
   373  	}
   374  	if v != "" {
   375  		if strings.Index(v, "/") != -1 {
   376  			// Bogus schema blob; ignore.
   377  			return ""
   378  		}
   379  		if strings.Index(v, "\\") != -1 {
   380  			// Bogus schema blob; ignore.
   381  			return ""
   382  		}
   383  	}
   384  	return v
   385  }
   386  
   387  func (ss *superset) HasFilename(name string) bool {
   388  	return ss.FileNameString() == name
   389  }
   390  
   391  func (b *Blob) FileMode() os.FileMode {
   392  	// TODO: move this to a different type, off *Blob
   393  	return b.ss.FileMode()
   394  }
   395  
   396  func (ss *superset) FileMode() os.FileMode {
   397  	var mode os.FileMode
   398  	m64, err := strconv.ParseUint(ss.UnixPermission, 8, 64)
   399  	if err == nil {
   400  		mode = mode | os.FileMode(m64)
   401  	}
   402  
   403  	// TODO: add other types (block, char, etc)
   404  	switch ss.Type {
   405  	case "directory":
   406  		mode = mode | os.ModeDir
   407  	case "file":
   408  		// No extra bit.
   409  	case "symlink":
   410  		mode = mode | os.ModeSymlink
   411  	}
   412  	return mode
   413  }
   414  
   415  // MapUid returns the most appropriate mapping from this file's owner
   416  // to the local machine's owner, trying first a match by name,
   417  // followed by just mapping the number through directly.
   418  func (b *Blob) MapUid() int { return b.ss.MapUid() }
   419  
   420  // MapGid returns the most appropriate mapping from this file's group
   421  // to the local machine's group, trying first a match by name,
   422  // followed by just mapping the number through directly.
   423  func (b *Blob) MapGid() int { return b.ss.MapGid() }
   424  
   425  func (ss *superset) MapUid() int {
   426  	if ss.UnixOwner != "" {
   427  		uid, ok := getUidFromName(ss.UnixOwner)
   428  		if ok {
   429  			return uid
   430  		}
   431  	}
   432  	return ss.UnixOwnerId // TODO: will be 0 if unset, which isn't ideal
   433  }
   434  
   435  func (ss *superset) MapGid() int {
   436  	if ss.UnixGroup != "" {
   437  		gid, ok := getGidFromName(ss.UnixGroup)
   438  		if ok {
   439  			return gid
   440  		}
   441  	}
   442  	return ss.UnixGroupId // TODO: will be 0 if unset, which isn't ideal
   443  }
   444  
   445  func (ss *superset) ModTime() time.Time {
   446  	if ss.UnixMtime == "" {
   447  		return time.Time{}
   448  	}
   449  	t, err := time.Parse(time.RFC3339, ss.UnixMtime)
   450  	if err != nil {
   451  		return time.Time{}
   452  	}
   453  	return t
   454  }
   455  
   456  var DefaultStatHasher = &defaultStatHasher{}
   457  
   458  type defaultStatHasher struct{}
   459  
   460  func (d *defaultStatHasher) Lstat(fileName string) (os.FileInfo, error) {
   461  	return os.Lstat(fileName)
   462  }
   463  
   464  func (d *defaultStatHasher) Hash(fileName string) (blob.Ref, error) {
   465  	s1 := sha1.New()
   466  	file, err := os.Open(fileName)
   467  	if err != nil {
   468  		return blob.Ref{}, err
   469  	}
   470  	defer file.Close()
   471  	_, err = io.Copy(s1, file)
   472  	if err != nil {
   473  		return blob.Ref{}, err
   474  	}
   475  	return blob.RefFromHash(s1), nil
   476  }
   477  
   478  type StaticSet struct {
   479  	l    sync.Mutex
   480  	refs []blob.Ref
   481  }
   482  
   483  func (ss *StaticSet) Add(ref blob.Ref) {
   484  	ss.l.Lock()
   485  	defer ss.l.Unlock()
   486  	ss.refs = append(ss.refs, ref)
   487  }
   488  
   489  func base(version int, ctype string) *Builder {
   490  	return &Builder{map[string]interface{}{
   491  		"camliVersion": version,
   492  		"camliType":    ctype,
   493  	}}
   494  }
   495  
   496  // NewUnsignedPermanode returns a new random permanode, not yet signed.
   497  func NewUnsignedPermanode() *Builder {
   498  	bb := base(1, "permanode")
   499  	chars := make([]byte, 20)
   500  	_, err := io.ReadFull(rand.Reader, chars)
   501  	if err != nil {
   502  		panic("error reading random bytes: " + err.Error())
   503  	}
   504  	bb.m["random"] = base64.StdEncoding.EncodeToString(chars)
   505  	return bb
   506  }
   507  
   508  // NewPlannedPermanode returns a permanode with a fixed key.  Like
   509  // NewUnsignedPermanode, this builder is also not yet signed.  Callers of
   510  // NewPlannedPermanode must sign the map with a fixed claimDate and
   511  // GPG date to create consistent JSON encodings of the Map (its
   512  // blobref), between runs.
   513  func NewPlannedPermanode(key string) *Builder {
   514  	bb := base(1, "permanode")
   515  	bb.m["key"] = key
   516  	return bb
   517  }
   518  
   519  // NewHashPlannedPermanode returns a planned permanode with the sum
   520  // of the hash, prefixed with "sha1-", as the key.
   521  func NewHashPlannedPermanode(h hash.Hash) *Builder {
   522  	if reflect.TypeOf(h) != sha1Type {
   523  		panic("Hash not supported. Only sha1 for now.")
   524  	}
   525  	return NewPlannedPermanode(fmt.Sprintf("sha1-%x", h.Sum(nil)))
   526  }
   527  
   528  // Map returns a Camli map of camliType "static-set"
   529  // TODO: delete this method
   530  func (ss *StaticSet) Blob() *Blob {
   531  	bb := base(1, "static-set")
   532  	ss.l.Lock()
   533  	defer ss.l.Unlock()
   534  
   535  	members := make([]string, 0, len(ss.refs))
   536  	if ss.refs != nil {
   537  		for _, ref := range ss.refs {
   538  			members = append(members, ref.String())
   539  		}
   540  	}
   541  	bb.m["members"] = members
   542  	return bb.Blob()
   543  }
   544  
   545  // JSON returns the map m encoded as JSON in its
   546  // recommended canonical form. The canonical form is readable with newlines and indentation,
   547  // and always starts with the header bytes:
   548  //
   549  //   {"camliVersion":
   550  //
   551  func mapJSON(m map[string]interface{}) (string, error) {
   552  	version, hasVersion := m["camliVersion"]
   553  	if !hasVersion {
   554  		return "", ErrNoCamliVersion
   555  	}
   556  	delete(m, "camliVersion")
   557  	jsonBytes, err := json.MarshalIndent(m, "", "  ")
   558  	if err != nil {
   559  		return "", err
   560  	}
   561  	m["camliVersion"] = version
   562  	var buf bytes.Buffer
   563  	fmt.Fprintf(&buf, "{\"camliVersion\": %v,\n", version)
   564  	buf.Write(jsonBytes[2:])
   565  	return buf.String(), nil
   566  }
   567  
   568  // NewFileMap returns a new builder of a type "file" schema for the provided fileName.
   569  // The chunk parts of the file are not populated.
   570  func NewFileMap(fileName string) *Builder {
   571  	return newCommonFilenameMap(fileName).SetType("file")
   572  }
   573  
   574  // NewDirMap returns a new builder of a type "directory" schema for the provided fileName.
   575  func NewDirMap(fileName string) *Builder {
   576  	return newCommonFilenameMap(fileName).SetType("directory")
   577  }
   578  
   579  func newCommonFilenameMap(fileName string) *Builder {
   580  	bb := base(1, "" /* no type yet */)
   581  	if fileName != "" {
   582  		bb.SetFileName(fileName)
   583  	}
   584  	return bb
   585  }
   586  
   587  var populateSchemaStat []func(schemaMap map[string]interface{}, fi os.FileInfo)
   588  
   589  func NewCommonFileMap(fileName string, fi os.FileInfo) *Builder {
   590  	bb := newCommonFilenameMap(fileName)
   591  	// Common elements (from file-common.txt)
   592  	if fi.Mode()&os.ModeSymlink == 0 {
   593  		bb.m["unixPermission"] = fmt.Sprintf("0%o", fi.Mode().Perm())
   594  	}
   595  
   596  	// OS-specific population; defined in schema_posix.go, etc. (not on App Engine)
   597  	for _, f := range populateSchemaStat {
   598  		f(bb.m, fi)
   599  	}
   600  
   601  	if mtime := fi.ModTime(); !mtime.IsZero() {
   602  		bb.m["unixMtime"] = RFC3339FromTime(mtime)
   603  	}
   604  	return bb
   605  }
   606  
   607  // PopulateParts sets the "parts" field of the blob with the provided
   608  // parts.  The sum of the sizes of parts must match the provided size
   609  // or an error is returned.  Also, each BytesPart may only contain either
   610  // a BytesPart or a BlobRef, but not both.
   611  func (bb *Builder) PopulateParts(size int64, parts []BytesPart) error {
   612  	return populateParts(bb.m, size, parts)
   613  }
   614  
   615  func populateParts(m map[string]interface{}, size int64, parts []BytesPart) error {
   616  	sumSize := int64(0)
   617  	mparts := make([]map[string]interface{}, len(parts))
   618  	for idx, part := range parts {
   619  		mpart := make(map[string]interface{})
   620  		mparts[idx] = mpart
   621  		switch {
   622  		case part.BlobRef.Valid() && part.BytesRef.Valid():
   623  			return errors.New("schema: part contains both BlobRef and BytesRef")
   624  		case part.BlobRef.Valid():
   625  			mpart["blobRef"] = part.BlobRef.String()
   626  		case part.BytesRef.Valid():
   627  			mpart["bytesRef"] = part.BytesRef.String()
   628  		default:
   629  			return errors.New("schema: part must contain either a BlobRef or BytesRef")
   630  		}
   631  		mpart["size"] = part.Size
   632  		sumSize += int64(part.Size)
   633  		if part.Offset != 0 {
   634  			mpart["offset"] = part.Offset
   635  		}
   636  	}
   637  	if sumSize != size {
   638  		return fmt.Errorf("schema: declared size %d doesn't match sum of parts size %d", size, sumSize)
   639  	}
   640  	m["parts"] = mparts
   641  	return nil
   642  }
   643  
   644  func newBytes() *Builder {
   645  	return base(1, "bytes")
   646  }
   647  
   648  // ClaimType is one of the valid "claimType" fields in a "claim" schema blob. See doc/schema/claims/.
   649  type ClaimType string
   650  
   651  const (
   652  	SetAttributeClaim ClaimType = "set-attribute"
   653  	AddAttributeClaim ClaimType = "add-attribute"
   654  	DelAttributeClaim ClaimType = "del-attribute"
   655  	ShareClaim        ClaimType = "share"
   656  	// DeleteClaim deletes a permanode or another claim.
   657  	// A delete claim can itself be deleted, and so on.
   658  	DeleteClaim ClaimType = "delete"
   659  )
   660  
   661  // claimParam is used to populate a claim map when building a new claim
   662  type claimParam struct {
   663  	claimType ClaimType
   664  
   665  	// Params specific to *Attribute claims:
   666  	permanode blob.Ref // modified permanode
   667  	attribute string   // required
   668  	value     string   // optional if Type == DelAttributeClaim
   669  
   670  	// Params specific to ShareClaim claims:
   671  	authType     string
   672  	transitive   bool
   673  	shareExpires time.Time // Zero means no expiration
   674  
   675  	// Params specific to ShareClaim and DeleteClaim claims.
   676  	target blob.Ref
   677  }
   678  
   679  func NewClaim(claims ...*claimParam) *Builder {
   680  	bb := base(1, "claim")
   681  	bb.SetClaimDate(clockNow())
   682  	if len(claims) == 1 {
   683  		cp := claims[0]
   684  		populateClaimMap(bb.m, cp)
   685  		return bb
   686  	}
   687  	var claimList []interface{}
   688  	for _, cp := range claims {
   689  		m := map[string]interface{}{}
   690  		populateClaimMap(m, cp)
   691  		claimList = append(claimList, m)
   692  	}
   693  	bb.m["claimType"] = "multi"
   694  	bb.m["claims"] = claimList
   695  	return bb
   696  }
   697  
   698  func populateClaimMap(m map[string]interface{}, cp *claimParam) {
   699  	m["claimType"] = string(cp.claimType)
   700  	switch cp.claimType {
   701  	case ShareClaim:
   702  		m["authType"] = cp.authType
   703  		m["target"] = cp.target.String()
   704  		m["transitive"] = cp.transitive
   705  	case DeleteClaim:
   706  		m["target"] = cp.target.String()
   707  	default:
   708  		m["permaNode"] = cp.permanode.String()
   709  		m["attribute"] = cp.attribute
   710  		if !(cp.claimType == DelAttributeClaim && cp.value == "") {
   711  			m["value"] = cp.value
   712  		}
   713  	}
   714  }
   715  
   716  // NewShareRef creates a *Builder for a "share" claim.
   717  func NewShareRef(authType string, target blob.Ref, transitive bool) *Builder {
   718  	return NewClaim(&claimParam{
   719  		claimType:  ShareClaim,
   720  		authType:   authType,
   721  		target:     target,
   722  		transitive: transitive,
   723  	})
   724  }
   725  
   726  func NewSetAttributeClaim(permaNode blob.Ref, attr, value string) *Builder {
   727  	return NewClaim(&claimParam{
   728  		permanode: permaNode,
   729  		claimType: SetAttributeClaim,
   730  		attribute: attr,
   731  		value:     value,
   732  	})
   733  }
   734  
   735  func NewAddAttributeClaim(permaNode blob.Ref, attr, value string) *Builder {
   736  	return NewClaim(&claimParam{
   737  		permanode: permaNode,
   738  		claimType: AddAttributeClaim,
   739  		attribute: attr,
   740  		value:     value,
   741  	})
   742  }
   743  
   744  // NewDelAttributeClaim creates a new claim to remove value from the
   745  // values set for the attribute attr of permaNode. If value is empty then
   746  // all the values for attribute are cleared.
   747  func NewDelAttributeClaim(permaNode blob.Ref, attr, value string) *Builder {
   748  	return NewClaim(&claimParam{
   749  		permanode: permaNode,
   750  		claimType: DelAttributeClaim,
   751  		attribute: attr,
   752  		value:     value,
   753  	})
   754  }
   755  
   756  // NewDeleteClaim creates a new claim to delete a target claim or permanode.
   757  func NewDeleteClaim(target blob.Ref) *Builder {
   758  	return NewClaim(&claimParam{
   759  		target:    target,
   760  		claimType: DeleteClaim,
   761  	})
   762  }
   763  
   764  // ShareHaveRef is the auth type specifying that if you "have the
   765  // reference" (know the blobref to the haveref share blob), then you
   766  // have access to the referenced object from that share blob.
   767  // This is the "send a link to a friend" access model.
   768  const ShareHaveRef = "haveref"
   769  
   770  // RFC3339FromTime returns an RFC3339-formatted time in UTC.
   771  // Fractional seconds are only included if the time has fractional
   772  // seconds.
   773  func RFC3339FromTime(t time.Time) string {
   774  	if t.UnixNano()%1e9 == 0 {
   775  		return t.UTC().Format(time.RFC3339)
   776  	}
   777  	return t.UTC().Format(time.RFC3339Nano)
   778  }
   779  
   780  var bytesCamliVersion = []byte("camliVersion")
   781  
   782  // LikelySchemaBlob returns quickly whether buf likely contains (or is
   783  // the prefix of) a schema blob.
   784  func LikelySchemaBlob(buf []byte) bool {
   785  	if len(buf) == 0 || buf[0] != '{' {
   786  		return false
   787  	}
   788  	return bytes.Contains(buf, bytesCamliVersion)
   789  }
   790  
   791  // findSize checks if v is an *os.File or if it has
   792  // a Size() int64 method, to find its size.
   793  // It returns 0, false otherwise.
   794  func findSize(v interface{}) (size int64, ok bool) {
   795  	if fi, ok := v.(*os.File); ok {
   796  		v, _ = fi.Stat()
   797  	}
   798  	if sz, ok := v.(interface {
   799  		Size() int64
   800  	}); ok {
   801  		return sz.Size(), true
   802  	}
   803  	// For bytes.Reader, strings.Reader, etc:
   804  	if li, ok := v.(interface {
   805  		Len() int
   806  	}); ok {
   807  		ln := int64(li.Len()) // unread portion, typically
   808  		// If it's also a seeker, remove add any seek offset:
   809  		if sk, ok := v.(io.Seeker); ok {
   810  			if cur, err := sk.Seek(0, 1); err == nil {
   811  				ln += cur
   812  			}
   813  		}
   814  		return ln, true
   815  	}
   816  	return 0, false
   817  }
   818  
   819  // FileTime returns the best guess of the file's creation time (or modtime).
   820  // If the file doesn't have its own metadata indication the creation time (such as in EXIF),
   821  // FileTime uses the modification time from the file system.
   822  // It there was a valid EXIF but an error while trying to get a date from it,
   823  // it logs the error and tries the other methods.
   824  func FileTime(f io.ReaderAt) (time.Time, error) {
   825  	var ct time.Time
   826  	defaultTime := func() (time.Time, error) {
   827  		if osf, ok := f.(*os.File); ok {
   828  			fi, err := osf.Stat()
   829  			if err != nil {
   830  				return ct, fmt.Errorf("Failed to find a modtime: lstat: %v", err)
   831  			}
   832  			return fi.ModTime(), nil
   833  		}
   834  		return ct, errors.New("All methods failed to find a creation time or modtime.")
   835  	}
   836  
   837  	size, ok := findSize(f)
   838  	if !ok {
   839  		size = 256 << 10 // enough to get the EXIF
   840  	}
   841  	r := io.NewSectionReader(f, 0, size)
   842  	ex, err := exif.Decode(r)
   843  	if err != nil {
   844  		return defaultTime()
   845  	}
   846  	ct, err = ex.DateTime()
   847  	if err != nil {
   848  		return defaultTime()
   849  	}
   850  	return ct, nil
   851  }