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 }