1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
34 type MissingFieldError string
35
36 func (e MissingFieldError) Error() string {
37 return fmt.Sprintf("schema: missing field %q", string(e))
38 }
39
40
41 func IsMissingField(err error) bool {
42 var mfe MissingFieldError
43 return errors.As(err, &mfe)
44 }
45
46
47 type AnyBlob interface {
48 Blob() *Blob
49 }
50
51
52 type Buildable interface {
53 Builder() *Builder
54 }
55
56
57
58 type Blob struct {
59 br blob.Ref
60 str string
61 ss *superset
62 }
63
64
65 func (b *Blob) Type() CamliType { return b.ss.Type }
66
67
68 func (b *Blob) BlobRef() blob.Ref { return b.br }
69
70
71 func (b *Blob) JSON() string { return b.str }
72
73
74 func (b *Blob) Blob() *Blob { return b }
75
76
77
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
87
88 func (b *Blob) FileName() string {
89 return b.ss.FileNameString()
90 }
91
92
93
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
104
105 func (b *Blob) ByteParts() []BytesPart {
106
107
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
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
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
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
156
157
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
173
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
205 func (b *Blob) ModTime() time.Time { return b.ss.ModTime() }
206
207
208 type Claim struct {
209 b *Blob
210 }
211
212
213 func (c Claim) Blob() *Blob { return c.b }
214
215
216 func (c Claim) ClaimDateString() string { return c.b.ss.ClaimDate.String() }
217
218
219 func (c Claim) ClaimType() string { return c.b.ss.ClaimType }
220
221
222 func (c Claim) Attribute() string { return c.b.ss.Attribute }
223
224
225 func (c Claim) Value() string { return c.b.ss.Value }
226
227
228 func (c Claim) Signer() blob.Ref { return c.b.ss.Signer }
229
230
231 func (c Claim) Signature() string { return c.b.ss.Sig }
232
233
234
235
236 func (c Claim) ModifiedPermanode() blob.Ref {
237 return c.b.ss.Permanode
238 }
239
240
241
242
243
244 func (c Claim) Target() blob.Ref {
245 return c.b.ss.Target
246 }
247
248
249
250
251 type Share struct {
252 Claim
253 }
254
255
256 func (s Share) AuthType() string {
257 return s.b.ss.AuthType
258 }
259
260
261
262
263 func (s Share) IsTransitive() bool {
264 return s.b.ss.Transitive
265 }
266
267
268 func (s Share) IsExpired() bool {
269 t := time.Time(s.b.ss.Expires)
270 return !t.IsZero() && clockNow().After(t)
271 }
272
273
274
275 type StaticFile struct {
276 b *Blob
277 }
278
279
280 func (sf StaticFile) FileName() string {
281 return sf.b.ss.FileNameString()
282 }
283
284
285
286
287 func (b *Blob) AsStaticFile() (sf StaticFile, ok bool) {
288
289
290
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
300 type StaticFIFO struct {
301 StaticFile
302 }
303
304
305 type StaticSocket struct {
306 StaticFile
307 }
308
309
310 type StaticSymlink struct {
311
312
313 StaticFile
314 }
315
316
317
318
319 func (sl StaticSymlink) SymlinkTargetString() string {
320 return sl.StaticFile.b.ss.SymlinkTargetString()
321 }
322
323
324
325
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
335
336
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
346
347
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
357
358 type Builder struct {
359 m map[string]interface{}
360 }
361
362
363
364
365 func NewBuilder() *Builder {
366 return &Builder{map[string]interface{}{
367 "camliVersion": "1",
368 }}
369 }
370
371
372
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
382
383
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
393
394
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
420 func (bb *Builder) SetRawStringField(key, value string) *Builder {
421 bb.m[key] = value
422 return bb
423 }
424
425
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
445 func (bb *Builder) Builder() *Builder {
446 return &Builder{clone(bb.m).(map[string]interface{})}
447 }
448
449
450 func (bb *Builder) JSON() (string, error) {
451 return mapJSON(bb.m)
452 }
453
454
455
456 func (bb *Builder) SetSigner(signer blob.Ref) *Builder {
457 bb.m["camliSigner"] = signer.String()
458 return bb
459 }
460
461
462
463 func (bb *Builder) Sign(ctx context.Context, signer *Signer) (string, error) {
464 return bb.SignAt(ctx, signer, time.Time{})
465 }
466
467
468
469
470
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
485 func (bb *Builder) SetType(t CamliType) *Builder {
486 bb.m["camliType"] = string(t)
487 return bb
488 }
489
490
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
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
507
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
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
530
531 func (bb *Builder) IsClaimType() bool {
532 switch bb.Type() {
533 case TypeClaim, TypePermanode:
534 return true
535 }
536 return false
537 }
538
539
540
541 func (bb *Builder) SetClaimDate(t time.Time) *Builder {
542 if !bb.IsClaimType() {
543
544
545
546
547 panic("SetClaimDate called on non-claim *Builder; camliType=" + bb.Type())
548 }
549 bb.m["claimDate"] = RFC3339FromTime(t)
550 return bb
551 }
552
553
554 func (bb *Builder) SetModTime(t time.Time) *Builder {
555 bb.m["unixMtime"] = RFC3339FromTime(t)
556 return bb
557 }
558
559
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
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
586
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
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 }