Home Download Docs Code Community
     1	/*
     2	Copyright 2013 The Perkeep Authors
     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		"context"
    21		"encoding/json"
    22		"fmt"
    23		"io"
    24		"path/filepath"
    25		"strings"
    26		"time"
    27		"unicode/utf8"
    28	
    29		"perkeep.org/pkg/blob"
    30	)
    31	
    32	// A MissingFieldError represents a missing JSON field in a schema blob.
    33	type MissingFieldError string
    34	
    35	func (e MissingFieldError) Error() string {
    36		return fmt.Sprintf("schema: missing field %q", string(e))
    37	}
    38	
    39	// IsMissingField returns whether error is of type MissingFieldError.
    40	func IsMissingField(err error) bool {
    41		_, ok := err.(MissingFieldError)
    42		return ok
    43	}
    44	
    45	// AnyBlob represents any type of schema blob.
    46	type AnyBlob interface {
    47		Blob() *Blob
    48	}
    49	
    50	// Buildable returns a Builder from a base.
    51	type Buildable interface {
    52		Builder() *Builder
    53	}
    54	
    55	// A Blob represents a Perkeep schema blob.
    56	// It is immutable.
    57	type Blob struct {
    58		br  blob.Ref
    59		str string
    60		ss  *superset
    61	}
    62	
    63	// Type returns the blob's "camliType" field.
    64	func (b *Blob) Type() CamliType { return b.ss.Type }
    65	
    66	// BlobRef returns the schema blob's blobref.
    67	func (b *Blob) BlobRef() blob.Ref { return b.br }
    68	
    69	// JSON returns the JSON bytes of the schema blob.
    70	func (b *Blob) JSON() string { return b.str }
    71	
    72	// Blob returns itself, so it satisfies the AnyBlob interface.
    73	func (b *Blob) Blob() *Blob { return b }
    74	
    75	// PartsSize returns the number of bytes represented by the "parts" field.
    76	// TODO: move this off *Blob to a specialized type.
    77	func (b *Blob) PartsSize() int64 {
    78		n := int64(0)
    79		for _, part := range b.ss.Parts {
    80			n += int64(part.Size)
    81		}
    82		return n
    83	}
    84	
    85	// FileName returns the file, directory, or symlink's filename, or the empty string.
    86	// TODO: move this off *Blob to a specialized type.
    87	func (b *Blob) FileName() string {
    88		return b.ss.FileNameString()
    89	}
    90	
    91	// ClaimDate returns the "claimDate" field.
    92	// If there is no claimDate, the error will be a MissingFieldError.
    93	func (b *Blob) ClaimDate() (time.Time, error) {
    94		var ct time.Time
    95		claimDate := b.ss.ClaimDate
    96		if claimDate.IsAnyZero() {
    97			return ct, MissingFieldError("claimDate")
    98		}
    99		return claimDate.Time(), nil
   100	}
   101	
   102	// ByteParts returns the "parts" field. The caller owns the returned
   103	// slice.
   104	func (b *Blob) ByteParts() []BytesPart {
   105		// TODO: move this method off Blob, and make the caller go
   106		// through a (*Blob).ByteBackedBlob() comma-ok accessor first.
   107		s := make([]BytesPart, len(b.ss.Parts))
   108		for i, part := range b.ss.Parts {
   109			s[i] = *part
   110		}
   111		return s
   112	}
   113	
   114	func (b *Blob) Builder() *Builder {
   115		var m map[string]interface{}
   116		dec := json.NewDecoder(strings.NewReader(b.str))
   117		dec.UseNumber()
   118		err := dec.Decode(&m)
   119		if err != nil {
   120			panic("failed to decode previously-thought-valid Blob's JSON: " + err.Error())
   121		}
   122		return &Builder{m}
   123	}
   124	
   125	// AsClaim returns a Claim if the receiver Blob has all the required fields.
   126	func (b *Blob) AsClaim() (c Claim, ok bool) {
   127		if b.ss.Signer.Valid() && b.ss.Sig != "" && b.ss.ClaimType != "" && !b.ss.ClaimDate.IsAnyZero() {
   128			return Claim{b}, true
   129		}
   130		return
   131	}
   132	
   133	// AsShare returns a Share if the receiver Blob has all the required fields.
   134	func (b *Blob) AsShare() (s Share, ok bool) {
   135		c, isClaim := b.AsClaim()
   136		if !isClaim {
   137			return
   138		}
   139	
   140		if ClaimType(b.ss.ClaimType) == ShareClaim && b.ss.AuthType == ShareHaveRef && (b.ss.Target.Valid() || b.ss.Search != nil) {
   141			return Share{c}, true
   142		}
   143		return s, false
   144	}
   145	
   146	// DirectoryEntries the "entries" field if valid and b's type is "directory".
   147	func (b *Blob) DirectoryEntries() (br blob.Ref, ok bool) {
   148		if b.Type() != TypeDirectory {
   149			return
   150		}
   151		return b.ss.Entries, true
   152	}
   153	
   154	// StaticSetMembers returns the refs in the "members" field if b is a valid
   155	// "static-set" schema. Note that if it is a large static-set, the members are
   156	// actually spread as subsets in "mergeSets". See StaticSetMergeSets.
   157	func (b *Blob) StaticSetMembers() []blob.Ref {
   158		if b.Type() != TypeStaticSet {
   159			return nil
   160		}
   161	
   162		s := make([]blob.Ref, 0, len(b.ss.Members))
   163		for _, ref := range b.ss.Members {
   164			if ref.Valid() {
   165				s = append(s, ref)
   166			}
   167		}
   168		return s
   169	}
   170	
   171	// StaticSetMergeSets returns the refs of the static-sets in "mergeSets". These
   172	// are the subsets of all the static-set members in the case of a large directory.
   173	func (b *Blob) StaticSetMergeSets() []blob.Ref {
   174		if b.Type() != TypeStaticSet {
   175			return nil
   176		}
   177	
   178		s := make([]blob.Ref, 0, len(b.ss.MergeSets))
   179		for _, ref := range b.ss.MergeSets {
   180			if ref.Valid() {
   181				s = append(s, ref)
   182			}
   183		}
   184		return s
   185	}
   186	
   187	func (b *Blob) ShareAuthType() string {
   188		s, ok := b.AsShare()
   189		if !ok {
   190			return ""
   191		}
   192		return s.AuthType()
   193	}
   194	
   195	func (b *Blob) ShareTarget() blob.Ref {
   196		s, ok := b.AsShare()
   197		if !ok {
   198			return blob.Ref{}
   199		}
   200		return s.Target()
   201	}
   202	
   203	// ModTime returns the "unixMtime" field, or the zero time.
   204	func (b *Blob) ModTime() time.Time { return b.ss.ModTime() }
   205	
   206	// A Claim is a Blob that is signed.
   207	type Claim struct {
   208		b *Blob
   209	}
   210	
   211	// Blob returns the claim's Blob.
   212	func (c Claim) Blob() *Blob { return c.b }
   213	
   214	// ClaimDateString returns the blob's "claimDate" field.
   215	func (c Claim) ClaimDateString() string { return c.b.ss.ClaimDate.String() }
   216	
   217	// ClaimType returns the blob's "claimType" field.
   218	func (c Claim) ClaimType() string { return c.b.ss.ClaimType }
   219	
   220	// Attribute returns the "attribute" field, if set.
   221	func (c Claim) Attribute() string { return c.b.ss.Attribute }
   222	
   223	// Value returns the "value" field, if set.
   224	func (c Claim) Value() string { return c.b.ss.Value }
   225	
   226	// Signer returns the ref of the blob containing the signing key that signed the claim.
   227	func (c Claim) Signer() blob.Ref { return c.b.ss.Signer }
   228	
   229	// Signature returns the claim's signature.
   230	func (c Claim) Signature() string { return c.b.ss.Sig }
   231	
   232	// ModifiedPermanode returns the claim's "permaNode" field, if it's
   233	// a claim that modifies a permanode. Otherwise a zero blob.Ref is
   234	// returned.
   235	func (c Claim) ModifiedPermanode() blob.Ref {
   236		return c.b.ss.Permanode
   237	}
   238	
   239	// Target returns the blob referenced by the Share if it's
   240	// a ShareClaim claim, or the object being deleted if it's a
   241	// DeleteClaim claim.
   242	// Otherwise a zero blob.Ref is returned.
   243	func (c Claim) Target() blob.Ref {
   244		return c.b.ss.Target
   245	}
   246	
   247	// A Share is a claim for giving access to a user's blob(s).
   248	// When returned from (*Blob).AsShare, it always represents
   249	// a valid share with all required fields.
   250	type Share struct {
   251		Claim
   252	}
   253	
   254	// AuthType returns the AuthType of the Share.
   255	func (s Share) AuthType() string {
   256		return s.b.ss.AuthType
   257	}
   258	
   259	// IsTransitive returns whether the Share transitively
   260	// gives access to everything reachable from the referenced
   261	// blob.
   262	func (s Share) IsTransitive() bool {
   263		return s.b.ss.Transitive
   264	}
   265	
   266	// IsExpired reports whether this share has expired.
   267	func (s Share) IsExpired() bool {
   268		t := time.Time(s.b.ss.Expires)
   269		return !t.IsZero() && clockNow().After(t)
   270	}
   271	
   272	// A StaticFile is a Blob representing a file, symlink fifo or socket
   273	// (or device file, when support for these is added).
   274	type StaticFile struct {
   275		b *Blob
   276	}
   277	
   278	// FileName returns the StaticFile's FileName if is not the empty string, otherwise it returns its FileNameBytes concatenated into a string.
   279	func (sf StaticFile) FileName() string {
   280		return sf.b.ss.FileNameString()
   281	}
   282	
   283	// AsStaticFile returns the Blob as a StaticFile if it represents
   284	// one. Otherwise, it returns false in the boolean parameter and the
   285	// zero value of StaticFile.
   286	func (b *Blob) AsStaticFile() (sf StaticFile, ok bool) {
   287		// TODO (marete) Add support for device files to
   288		// Perkeep and change the implementation of StaticFile to
   289		// reflect that.
   290		t := b.ss.Type
   291		if t == TypeFile || t == TypeSymlink || t == TypeFIFO || t == TypeSocket {
   292			return StaticFile{b}, true
   293		}
   294	
   295		return
   296	}
   297	
   298	// A StaticFIFO is a StaticFile that is also a fifo.
   299	type StaticFIFO struct {
   300		StaticFile
   301	}
   302	
   303	// A StaticSocket is a StaticFile that is also a socket.
   304	type StaticSocket struct {
   305		StaticFile
   306	}
   307	
   308	// A StaticSymlink is a StaticFile that is also a symbolic link.
   309	type StaticSymlink struct {
   310		// We name it `StaticSymlink' rather than just `Symlink' since
   311		// a type called Symlink is already in schema.go.
   312		StaticFile
   313	}
   314	
   315	// SymlinkTargetString returns the field symlinkTarget if is
   316	// non-empty. Otherwise it returns the contents of symlinkTargetBytes
   317	// concatenated as a string.
   318	func (sl StaticSymlink) SymlinkTargetString() string {
   319		return sl.StaticFile.b.ss.SymlinkTargetString()
   320	}
   321	
   322	// AsStaticSymlink returns the StaticFile as a StaticSymlink if the
   323	// StaticFile represents a symlink. Otherwise, it returns the zero
   324	// value of StaticSymlink and false.
   325	func (sf StaticFile) AsStaticSymlink() (s StaticSymlink, ok bool) {
   326		if sf.b.ss.Type == TypeSymlink {
   327			return StaticSymlink{sf}, true
   328		}
   329	
   330		return
   331	}
   332	
   333	// AsStaticFIFO returns the StatifFile as a StaticFIFO if the
   334	// StaticFile represents a fifo. Otherwise, it returns the zero value
   335	// of StaticFIFO and false.
   336	func (sf StaticFile) AsStaticFIFO() (fifo StaticFIFO, ok bool) {
   337		if sf.b.ss.Type == TypeFIFO {
   338			return StaticFIFO{sf}, true
   339		}
   340	
   341		return
   342	}
   343	
   344	// AsStaticSocket returns the StaticFile as a StaticSocket if the
   345	// StaticFile represents a socket. Otherwise, it returns the zero
   346	// value of StaticSocket and false.
   347	func (sf StaticFile) AsStaticSocket() (ss StaticSocket, ok bool) {
   348		if sf.b.ss.Type == TypeSocket {
   349			return StaticSocket{sf}, true
   350		}
   351	
   352		return
   353	}
   354	
   355	// A Builder builds a JSON blob.
   356	// After mutating the Builder, call Blob to get the built blob.
   357	type Builder struct {
   358		m map[string]interface{}
   359	}
   360	
   361	// NewBuilder returns a new blob schema builder.
   362	// The "camliVersion" field is set to "1" by default and the required
   363	// "camliType" field is NOT set.
   364	func NewBuilder() *Builder {
   365		return &Builder{map[string]interface{}{
   366			"camliVersion": "1",
   367		}}
   368	}
   369	
   370	// SetShareTarget sets the target of share claim.
   371	// It panics if bb isn't a "share" claim type.
   372	func (bb *Builder) SetShareTarget(t blob.Ref) *Builder {
   373		if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
   374			panic("called SetShareTarget on non-share")
   375		}
   376		bb.m["target"] = t.String()
   377		return bb
   378	}
   379	
   380	// SetShareSearch sets the search of share claim.
   381	// q is assumed to be of type *search.SearchQuery.
   382	// It panics if bb isn't a "share" claim type.
   383	func (bb *Builder) SetShareSearch(q SearchQuery) *Builder {
   384		if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
   385			panic("called SetShareSearch on non-share")
   386		}
   387		bb.m["search"] = q
   388		return bb
   389	}
   390	
   391	// SetShareExpiration sets the expiration time on share claim.
   392	// It panics if bb isn't a "share" claim type.
   393	// If t is zero, the expiration is removed.
   394	func (bb *Builder) SetShareExpiration(t time.Time) *Builder {
   395		if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
   396			panic("called SetShareExpiration on non-share")
   397		}
   398		if t.IsZero() {
   399			delete(bb.m, "expires")
   400		} else {
   401			bb.m["expires"] = RFC3339FromTime(t)
   402		}
   403		return bb
   404	}
   405	
   406	func (bb *Builder) SetShareIsTransitive(b bool) *Builder {
   407		if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
   408			panic("called SetShareIsTransitive on non-share")
   409		}
   410		if !b {
   411			delete(bb.m, "transitive")
   412		} else {
   413			bb.m["transitive"] = true
   414		}
   415		return bb
   416	}
   417	
   418	// SetRawStringField sets a raw string field in the underlying map.
   419	func (bb *Builder) SetRawStringField(key, value string) *Builder {
   420		bb.m[key] = value
   421		return bb
   422	}
   423	
   424	// Blob builds the Blob. The builder continues to be usable after a call to Build.
   425	func (bb *Builder) Blob() *Blob {
   426		json, err := mapJSON(bb.m)
   427		if err != nil {
   428			panic(err)
   429		}
   430		ss, err := parseSuperset(strings.NewReader(json))
   431		if err != nil {
   432			panic(err)
   433		}
   434		h := blob.NewHash()
   435		io.WriteString(h, json)
   436		return &Blob{
   437			str: json,
   438			ss:  ss,
   439			br:  blob.RefFromHash(h),
   440		}
   441	}
   442	
   443	// Builder returns a clone of itself and satisfies the Buildable interface.
   444	func (bb *Builder) Builder() *Builder {
   445		return &Builder{clone(bb.m).(map[string]interface{})}
   446	}
   447	
   448	// JSON returns the JSON of the blob as built so far.
   449	func (bb *Builder) JSON() (string, error) {
   450		return mapJSON(bb.m)
   451	}
   452	
   453	// SetSigner sets the camliSigner field.
   454	// Calling SetSigner is unnecessary if using Sign.
   455	func (bb *Builder) SetSigner(signer blob.Ref) *Builder {
   456		bb.m["camliSigner"] = signer.String()
   457		return bb
   458	}
   459	
   460	// Sign sets the blob builder's camliSigner field with SetSigner
   461	// and returns the signed JSON using the provided signer.
   462	func (bb *Builder) Sign(ctx context.Context, signer *Signer) (string, error) {
   463		return bb.SignAt(ctx, signer, time.Time{})
   464	}
   465	
   466	// SignAt sets the blob builder's camliSigner field with SetSigner
   467	// and returns the signed JSON using the provided signer.
   468	// The provided sigTime is the time of the signature, used mostly
   469	// for planned permanodes. If the zero value, the current time is used.
   470	func (bb *Builder) SignAt(ctx context.Context, signer *Signer, sigTime time.Time) (string, error) {
   471		switch bb.Type() {
   472		case TypePermanode, TypeClaim:
   473		default:
   474			return "", fmt.Errorf("can't sign camliType %q", bb.Type())
   475		}
   476		if sigTime.IsZero() {
   477			sigTime = time.Now()
   478		}
   479		bb.SetClaimDate(sigTime)
   480		return signer.SignJSON(ctx, bb.SetSigner(signer.pubref).Blob().JSON(), sigTime)
   481	}
   482	
   483	// SetType sets the camliType field.
   484	func (bb *Builder) SetType(t CamliType) *Builder {
   485		bb.m["camliType"] = string(t)
   486		return bb
   487	}
   488	
   489	// Type returns the camliType value.
   490	func (bb *Builder) Type() CamliType {
   491		if s, ok := bb.m["camliType"].(string); ok {
   492			return CamliType(s)
   493		}
   494		return ""
   495	}
   496	
   497	// ClaimType returns the claimType value, or the empty string.
   498	func (bb *Builder) ClaimType() ClaimType {
   499		if s, ok := bb.m["claimType"].(string); ok {
   500			return ClaimType(s)
   501		}
   502		return ""
   503	}
   504	
   505	// SetFileName sets the fileName or fileNameBytes field.
   506	// The filename is truncated to just the base.
   507	func (bb *Builder) SetFileName(name string) *Builder {
   508		baseName := filepath.Base(name)
   509		if utf8.ValidString(baseName) {
   510			bb.m["fileName"] = baseName
   511		} else {
   512			bb.m["fileNameBytes"] = mixedArrayFromString(baseName)
   513		}
   514		return bb
   515	}
   516	
   517	// SetSymlinkTarget sets bb to be of type "symlink" and sets the symlink's target.
   518	func (bb *Builder) SetSymlinkTarget(target string) *Builder {
   519		bb.SetType(TypeSymlink)
   520		if utf8.ValidString(target) {
   521			bb.m["symlinkTarget"] = target
   522		} else {
   523			bb.m["symlinkTargetBytes"] = mixedArrayFromString(target)
   524		}
   525		return bb
   526	}
   527	
   528	// IsClaimType returns whether this blob builder is for a type
   529	// which should be signed. (a "claim" or "permanode")
   530	func (bb *Builder) IsClaimType() bool {
   531		switch bb.Type() {
   532		case TypeClaim, TypePermanode:
   533			return true
   534		}
   535		return false
   536	}
   537	
   538	// SetClaimDate sets the "claimDate" on a claim.
   539	// It is a fatal error to call SetClaimDate if the Map isn't of Type "claim".
   540	func (bb *Builder) SetClaimDate(t time.Time) *Builder {
   541		if !bb.IsClaimType() {
   542			// This is a little gross, using panic here, but I
   543			// don't want all callers to check errors.  This is
   544			// really a programming error, not a runtime error
   545			// that would arise from e.g. random user data.
   546			panic("SetClaimDate called on non-claim *Builder; camliType=" + bb.Type())
   547		}
   548		bb.m["claimDate"] = RFC3339FromTime(t)
   549		return bb
   550	}
   551	
   552	// SetModTime sets the "unixMtime" field.
   553	func (bb *Builder) SetModTime(t time.Time) *Builder {
   554		bb.m["unixMtime"] = RFC3339FromTime(t)
   555		return bb
   556	}
   557	
   558	// CapCreationTime caps the "unixCtime" field to be less or equal than "unixMtime"
   559	func (bb *Builder) CapCreationTime() *Builder {
   560		ctime, ok := bb.m["unixCtime"].(string)
   561		if !ok {
   562			return bb
   563		}
   564		mtime, ok := bb.m["unixMtime"].(string)
   565		if ok && ctime > mtime {
   566			bb.m["unixCtime"] = mtime
   567		}
   568		return bb
   569	}
   570	
   571	// ModTime returns the "unixMtime" modtime field, if set.
   572	func (bb *Builder) ModTime() (t time.Time, ok bool) {
   573		s, ok := bb.m["unixMtime"].(string)
   574		if !ok {
   575			return
   576		}
   577		t, err := time.Parse(time.RFC3339, s)
   578		if err != nil {
   579			return
   580		}
   581		return t, true
   582	}
   583	
   584	// PopulateDirectoryMap sets the type of *Builder to "directory" and sets
   585	// the "entries" field to the provided staticSet blobref.
   586	func (bb *Builder) PopulateDirectoryMap(staticSetRef blob.Ref) *Builder {
   587		bb.m["camliType"] = string(TypeDirectory)
   588		bb.m["entries"] = staticSetRef.String()
   589		return bb
   590	}
   591	
   592	// PartsSize returns the number of bytes represented by the "parts" field.
   593	func (bb *Builder) PartsSize() int64 {
   594		n := int64(0)
   595		if parts, ok := bb.m["parts"].([]BytesPart); ok {
   596			for _, part := range parts {
   597				n += int64(part.Size)
   598			}
   599		}
   600		return n
   601	}
   602	
   603	func clone(i interface{}) interface{} {
   604		switch t := i.(type) {
   605		case map[string]interface{}:
   606			m2 := make(map[string]interface{})
   607			for k, v := range t {
   608				m2[k] = clone(v)
   609			}
   610			return m2
   611		case string, int, int64, float64, json.Number:
   612			return t
   613		case []interface{}:
   614			s2 := make([]interface{}, len(t))
   615			for i, v := range t {
   616				s2[i] = clone(v)
   617			}
   618			return s2
   619		}
   620		panic(fmt.Sprintf("unsupported clone type %T", i))
   621	}
Website layout inspired by memcached.
Content by the authors.