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