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

     1  /*
     2  Copyright 2013 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
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"path/filepath"
    23  	"strings"
    24  	"time"
    25  	"unicode/utf8"
    26  
    27  	"camlistore.org/pkg/blob"
    28  )
    29  
    30  // A MissingFieldError represents a missing JSON field in a schema blob.
    31  type MissingFieldError string
    32  
    33  func (e MissingFieldError) Error() string {
    34  	return fmt.Sprintf("schema: missing field %q", string(e))
    35  }
    36  
    37  // IsMissingField returns whether error is of type MissingFieldError.
    38  func IsMissingField(err error) bool {
    39  	_, ok := err.(MissingFieldError)
    40  	return ok
    41  }
    42  
    43  // AnyBlob represents any type of schema blob.
    44  type AnyBlob interface {
    45  	Blob() *Blob
    46  }
    47  
    48  // Buildable returns a Builder from a base.
    49  type Buildable interface {
    50  	Builder() *Builder
    51  }
    52  
    53  // A Blob represents a Camlistore schema blob.
    54  // It is immutable.
    55  type Blob struct {
    56  	br  blob.Ref
    57  	str string
    58  	ss  *superset
    59  }
    60  
    61  // Type returns the blob's "camliType" field.
    62  func (b *Blob) Type() string { return b.ss.Type }
    63  
    64  // BlobRef returns the schema blob's blobref.
    65  func (b *Blob) BlobRef() blob.Ref { return b.br }
    66  
    67  // JSON returns the JSON bytes of the schema blob.
    68  func (b *Blob) JSON() string { return b.str }
    69  
    70  // Blob returns itself, so it satisifies the AnyBlob interface.
    71  func (b *Blob) Blob() *Blob { return b }
    72  
    73  // PartsSize returns the number of bytes represented by the "parts" field.
    74  // TODO: move this off *Blob to a specialized type.
    75  func (b *Blob) PartsSize() int64 {
    76  	n := int64(0)
    77  	for _, part := range b.ss.Parts {
    78  		n += int64(part.Size)
    79  	}
    80  	return n
    81  }
    82  
    83  // FileName returns the file, directory, or symlink's filename, or the empty string.
    84  // TODO: move this off *Blob to a specialized type.
    85  func (b *Blob) FileName() string {
    86  	return b.ss.FileNameString()
    87  }
    88  
    89  // ClaimDate returns the "claimDate" field.
    90  // If there is no claimDate, the error will be a MissingFieldError.
    91  func (b *Blob) ClaimDate() (time.Time, error) {
    92  	var ct time.Time
    93  	claimDate := b.ss.ClaimDate
    94  	if claimDate.IsZero() {
    95  		return ct, MissingFieldError("claimDate")
    96  	}
    97  	return claimDate.Time(), nil
    98  }
    99  
   100  // ByteParts returns the "parts" field. The caller owns the returned
   101  // slice.
   102  func (b *Blob) ByteParts() []BytesPart {
   103  	// TODO: move this method off Blob, and make the caller go
   104  	// through a (*Blob).ByteBackedBlob() comma-ok accessor first.
   105  	s := make([]BytesPart, len(b.ss.Parts))
   106  	for i, part := range b.ss.Parts {
   107  		s[i] = *part
   108  	}
   109  	return s
   110  }
   111  
   112  func (b *Blob) Builder() *Builder {
   113  	var m map[string]interface{}
   114  	dec := json.NewDecoder(strings.NewReader(b.str))
   115  	dec.UseNumber()
   116  	err := dec.Decode(&m)
   117  	if err != nil {
   118  		panic("failed to decode previously-thought-valid Blob's JSON: " + err.Error())
   119  	}
   120  	return &Builder{m}
   121  }
   122  
   123  // AsClaim returns a Claim if the receiver Blob has all the required fields.
   124  func (b *Blob) AsClaim() (c Claim, ok bool) {
   125  	if b.ss.Signer.Valid() && b.ss.Sig != "" && b.ss.ClaimType != "" && !b.ss.ClaimDate.IsZero() {
   126  		return Claim{b}, true
   127  	}
   128  	return
   129  }
   130  
   131  // AsShare returns a Share if the receiver Blob has all the required fields.
   132  func (b *Blob) AsShare() (s Share, ok bool) {
   133  	c, isClaim := b.AsClaim()
   134  	if !isClaim {
   135  		return
   136  	}
   137  
   138  	if ClaimType(b.ss.ClaimType) == ShareClaim && b.ss.AuthType == ShareHaveRef && b.ss.Target.Valid() {
   139  		return Share{c}, true
   140  	}
   141  	return s, false
   142  }
   143  
   144  // DirectoryEntries the "entries" field if valid and b's type is "directory".
   145  func (b *Blob) DirectoryEntries() (br blob.Ref, ok bool) {
   146  	if b.Type() != "directory" {
   147  		return
   148  	}
   149  	return b.ss.Entries, true
   150  }
   151  
   152  func (b *Blob) StaticSetMembers() []blob.Ref {
   153  	if b.Type() != "static-set" {
   154  		return nil
   155  	}
   156  	s := make([]blob.Ref, 0, len(b.ss.Members))
   157  	for _, ref := range b.ss.Members {
   158  		if ref.Valid() {
   159  			s = append(s, ref)
   160  		}
   161  	}
   162  	return s
   163  }
   164  
   165  func (b *Blob) ShareAuthType() string {
   166  	s, ok := b.AsShare()
   167  	if !ok {
   168  		return ""
   169  	}
   170  	return s.AuthType()
   171  }
   172  
   173  func (b *Blob) ShareTarget() blob.Ref {
   174  	s, ok := b.AsShare()
   175  	if !ok {
   176  		return blob.Ref{}
   177  	}
   178  	return s.Target()
   179  }
   180  
   181  // ModTime returns the "unixMtime" field, or the zero time.
   182  func (b *Blob) ModTime() time.Time { return b.ss.ModTime() }
   183  
   184  // A Claim is a Blob that is signed.
   185  type Claim struct {
   186  	b *Blob
   187  }
   188  
   189  // Blob returns the claim's Blob.
   190  func (c Claim) Blob() *Blob { return c.b }
   191  
   192  // ClaimDate returns the blob's "claimDate" field.
   193  func (c Claim) ClaimDateString() string { return c.b.ss.ClaimDate.String() }
   194  
   195  // ClaimType returns the blob's "claimType" field.
   196  func (c Claim) ClaimType() string { return c.b.ss.ClaimType }
   197  
   198  // Attribute returns the "attribute" field, if set.
   199  func (c Claim) Attribute() string { return c.b.ss.Attribute }
   200  
   201  // Value returns the "value" field, if set.
   202  func (c Claim) Value() string { return c.b.ss.Value }
   203  
   204  // ModifiedPermanode returns the claim's "permaNode" field, if it's
   205  // a claim that modifies a permanode. Otherwise a zero blob.Ref is
   206  // returned.
   207  func (c Claim) ModifiedPermanode() blob.Ref {
   208  	return c.b.ss.Permanode
   209  }
   210  
   211  // Target returns the blob referenced by the Share if it's
   212  // a ShareClaim claim, or the object being deleted if it's a
   213  // DeleteClaim claim.
   214  // Otherwise a zero blob.Ref is returned.
   215  func (c Claim) Target() blob.Ref {
   216  	return c.b.ss.Target
   217  }
   218  
   219  // A Share is a claim for giving access to a user's blob(s).
   220  // When returned from (*Blob).AsShare, it always represents
   221  // a valid share with all required fields.
   222  type Share struct {
   223  	Claim
   224  }
   225  
   226  // AuthType returns the AuthType of the Share.
   227  func (s Share) AuthType() string {
   228  	return s.b.ss.AuthType
   229  }
   230  
   231  // IsTransitive returns whether the Share transitively
   232  // gives access to everything reachable from the referenced
   233  // blob.
   234  func (s Share) IsTransitive() bool {
   235  	return s.b.ss.Transitive
   236  }
   237  
   238  // IsExpired reports whether this share has expired.
   239  func (s Share) IsExpired() bool {
   240  	t := time.Time(s.b.ss.Expires)
   241  	return !t.IsZero() && clockNow().After(t)
   242  }
   243  
   244  // A Builder builds a JSON blob.
   245  // After mutating the Builder, call Blob to get the built blob.
   246  type Builder struct {
   247  	m map[string]interface{}
   248  }
   249  
   250  // NewBuilder returns a new blob schema builder.
   251  // The "camliVersion" field is set to "1" by default and the required
   252  // "camliType" field is NOT set.
   253  func NewBuilder() *Builder {
   254  	return &Builder{map[string]interface{}{
   255  		"camliVersion": "1",
   256  	}}
   257  }
   258  
   259  // SetShareExpiration sets the expiration time on share claim.
   260  // It panics if bb isn't a "share" claim type.
   261  // If t is zero, the expiration is removed.
   262  func (bb *Builder) SetShareExpiration(t time.Time) {
   263  	if bb.Type() != "claim" || bb.ClaimType() != ShareClaim {
   264  		panic("called SetShareExpiration on non-share")
   265  	}
   266  	if t.IsZero() {
   267  		delete(bb.m, "expires")
   268  	} else {
   269  		bb.m["expires"] = RFC3339FromTime(t)
   270  	}
   271  }
   272  
   273  func (bb *Builder) SetShareIsTransitive(b bool) {
   274  	if bb.Type() != "claim" || bb.ClaimType() != ShareClaim {
   275  		panic("called SetShareIsTransitive on non-share")
   276  	}
   277  	if !b {
   278  		delete(bb.m, "transitive")
   279  	} else {
   280  		bb.m["transitive"] = true
   281  	}
   282  }
   283  
   284  // SetRawStringField sets a raw string field in the underlying map.
   285  func (bb *Builder) SetRawStringField(key, value string) *Builder {
   286  	bb.m[key] = value
   287  	return bb
   288  }
   289  
   290  // Blob builds the Blob. The builder continues to be usable after a call to Build.
   291  func (bb *Builder) Blob() *Blob {
   292  	json, err := mapJSON(bb.m)
   293  	if err != nil {
   294  		panic(err)
   295  	}
   296  	ss, err := parseSuperset(strings.NewReader(json))
   297  	if err != nil {
   298  		panic(err)
   299  	}
   300  	h := blob.NewHash()
   301  	h.Write([]byte(json))
   302  	return &Blob{
   303  		str: json,
   304  		ss:  ss,
   305  		br:  blob.RefFromHash(h),
   306  	}
   307  }
   308  
   309  // Builder returns a clone of itself and satisifies the Buildable interface.
   310  func (bb *Builder) Builder() *Builder {
   311  	return &Builder{clone(bb.m).(map[string]interface{})}
   312  }
   313  
   314  // JSON returns the JSON of the blob as built so far.
   315  func (bb *Builder) JSON() (string, error) {
   316  	return mapJSON(bb.m)
   317  }
   318  
   319  // SetSigner sets the camliSigner field.
   320  // Calling SetSigner is unnecessary if using Sign.
   321  func (bb *Builder) SetSigner(signer blob.Ref) *Builder {
   322  	bb.m["camliSigner"] = signer.String()
   323  	return bb
   324  }
   325  
   326  // SignAt sets the blob builder's camliSigner field with SetSigner
   327  // and returns the signed JSON using the provided signer.
   328  func (bb *Builder) Sign(signer *Signer) (string, error) {
   329  	return bb.SignAt(signer, time.Time{})
   330  }
   331  
   332  // SignAt sets the blob builder's camliSigner field with SetSigner
   333  // and returns the signed JSON using the provided signer.
   334  // The provided sigTime is the time of the signature, used mostly
   335  // for planned permanodes. If the zero value, the current time is used.
   336  func (bb *Builder) SignAt(signer *Signer, sigTime time.Time) (string, error) {
   337  	switch bb.Type() {
   338  	case "permanode", "claim":
   339  	default:
   340  		return "", fmt.Errorf("can't sign camliType %q", bb.Type())
   341  	}
   342  	return signer.SignJSON(bb.SetSigner(signer.pubref).Blob().JSON(), sigTime)
   343  }
   344  
   345  // SetType sets the camliType field.
   346  func (bb *Builder) SetType(t string) *Builder {
   347  	bb.m["camliType"] = t
   348  	return bb
   349  }
   350  
   351  // Type returns the camliType value.
   352  func (bb *Builder) Type() string {
   353  	if s, ok := bb.m["camliType"].(string); ok {
   354  		return s
   355  	}
   356  	return ""
   357  }
   358  
   359  // ClaimType returns the claimType value, or the empty string.
   360  func (bb *Builder) ClaimType() ClaimType {
   361  	if s, ok := bb.m["claimType"].(string); ok {
   362  		return ClaimType(s)
   363  	}
   364  	return ""
   365  }
   366  
   367  // SetFileName sets the fileName or fileNameBytes field.
   368  // The filename is truncated to just the base.
   369  func (bb *Builder) SetFileName(name string) *Builder {
   370  	baseName := filepath.Base(name)
   371  	if utf8.ValidString(baseName) {
   372  		bb.m["fileName"] = baseName
   373  	} else {
   374  		bb.m["fileNameBytes"] = []uint8(baseName)
   375  	}
   376  	return bb
   377  }
   378  
   379  // SetSymlinkTarget sets bb to be of type "symlink" and sets the symlink's target.
   380  func (bb *Builder) SetSymlinkTarget(target string) *Builder {
   381  	bb.SetType("symlink")
   382  	if utf8.ValidString(target) {
   383  		bb.m["symlinkTarget"] = target
   384  	} else {
   385  		bb.m["symlinkTargetBytes"] = []uint8(target)
   386  	}
   387  	return bb
   388  }
   389  
   390  // IsClaimType returns whether this blob builder is for a type
   391  // which should be signed. (a "claim" or "permanode")
   392  func (bb *Builder) IsClaimType() bool {
   393  	switch bb.Type() {
   394  	case "claim", "permanode":
   395  		return true
   396  	}
   397  	return false
   398  }
   399  
   400  // SetClaimDate sets the "claimDate" on a claim.
   401  // It is a fatal error to call SetClaimDate if the Map isn't of Type "claim".
   402  func (bb *Builder) SetClaimDate(t time.Time) *Builder {
   403  	if !bb.IsClaimType() {
   404  		// This is a little gross, using panic here, but I
   405  		// don't want all callers to check errors.  This is
   406  		// really a programming error, not a runtime error
   407  		// that would arise from e.g. random user data.
   408  		panic("SetClaimDate called on non-claim *Builder; camliType=" + bb.Type())
   409  	}
   410  	bb.m["claimDate"] = RFC3339FromTime(t)
   411  	return bb
   412  }
   413  
   414  // SetModTime sets the "unixMtime" field.
   415  func (bb *Builder) SetModTime(t time.Time) *Builder {
   416  	bb.m["unixMtime"] = RFC3339FromTime(t)
   417  	return bb
   418  }
   419  
   420  // CapCreationTime caps the "unixCtime" field to be less or equal than "unixMtime"
   421  func (bb *Builder) CapCreationTime() *Builder {
   422  	ctime, ok := bb.m["unixCtime"].(string)
   423  	if !ok {
   424  		return bb
   425  	}
   426  	mtime, ok := bb.m["unixMtime"].(string)
   427  	if ok && ctime > mtime {
   428  		bb.m["unixCtime"] = mtime
   429  	}
   430  	return bb
   431  }
   432  
   433  // ModTime returns the "unixMtime" modtime field, if set.
   434  func (bb *Builder) ModTime() (t time.Time, ok bool) {
   435  	s, ok := bb.m["unixMtime"].(string)
   436  	if !ok {
   437  		return
   438  	}
   439  	t, err := time.Parse(time.RFC3339, s)
   440  	if err != nil {
   441  		return
   442  	}
   443  	return t, true
   444  }
   445  
   446  // PopulateDirectoryMap sets the type of *Builder to "directory" and sets
   447  // the "entries" field to the provided staticSet blobref.
   448  func (bb *Builder) PopulateDirectoryMap(staticSetRef blob.Ref) *Builder {
   449  	bb.m["camliType"] = "directory"
   450  	bb.m["entries"] = staticSetRef.String()
   451  	return bb
   452  }
   453  
   454  // PartsSize returns the number of bytes represented by the "parts" field.
   455  func (bb *Builder) PartsSize() int64 {
   456  	n := int64(0)
   457  	if parts, ok := bb.m["parts"].([]BytesPart); ok {
   458  		for _, part := range parts {
   459  			n += int64(part.Size)
   460  		}
   461  	}
   462  	return n
   463  }
   464  
   465  func clone(i interface{}) interface{} {
   466  	switch t := i.(type) {
   467  	case map[string]interface{}:
   468  		m2 := make(map[string]interface{})
   469  		for k, v := range t {
   470  			m2[k] = clone(v)
   471  		}
   472  		return m2
   473  	case string, int, int64, float64, json.Number:
   474  		return t
   475  	case []interface{}:
   476  		s2 := make([]interface{}, len(t))
   477  		for i, v := range t {
   478  			s2[i] = clone(v)
   479  		}
   480  		return s2
   481  	}
   482  	panic(fmt.Sprintf("unsupported clone type %T", i))
   483  }