1 package client
2
3 import (
4 "bytes"
5 "encoding/hex"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10
11 "github.com/theupdateframework/go-tuf/data"
12 "github.com/theupdateframework/go-tuf/internal/roles"
13 "github.com/theupdateframework/go-tuf/util"
14 "github.com/theupdateframework/go-tuf/verify"
15 )
16
17 const (
18
19
20
21 defaultRootDownloadLimit = 512000
22 defaultTimestampDownloadLimit = 16384
23 defaultMaxDelegations = 32
24 defaultMaxRootRotations = 1e3
25 )
26
27
28 type LocalStore interface {
29 io.Closer
30
31
32
33 GetMeta() (map[string]json.RawMessage, error)
34
35
36
37 SetMeta(name string, meta json.RawMessage) error
38
39
40 DeleteMeta(name string) error
41 }
42
43
44
45 type RemoteStore interface {
46
47
48
49
50
51
52
53 GetMeta(name string) (stream io.ReadCloser, size int64, err error)
54
55
56
57
58
59
60
61
62
63 GetTarget(path string) (stream io.ReadCloser, size int64, err error)
64 }
65
66
67
68 type Client struct {
69 local LocalStore
70 remote RemoteStore
71
72
73
74 rootVer int64
75 targetsVer int64
76 snapshotVer int64
77 timestampVer int64
78
79
80
81 targets data.TargetFiles
82
83
84
85 localMeta map[string]json.RawMessage
86
87
88 db *verify.DB
89
90
91
92 consistentSnapshot bool
93
94
95
96 MaxDelegations int
97
98
99 MaxRootRotations int
100 }
101
102 func NewClient(local LocalStore, remote RemoteStore) *Client {
103 return &Client{
104 local: local,
105 remote: remote,
106 MaxDelegations: defaultMaxDelegations,
107 MaxRootRotations: defaultMaxRootRotations,
108 }
109 }
110
111
112
113
114
115
116
117 func (c *Client) Init(rootJSON []byte) error {
118 err := c.loadAndVerifyRootMeta(rootJSON, true )
119 if err != nil {
120 return err
121 }
122 return c.local.SetMeta("root.json", rootJSON)
123 }
124
125
126
127
128
129 func (c *Client) Update() (data.TargetFiles, error) {
130 if err := c.UpdateRoots(); err != nil {
131 if _, ok := err.(verify.ErrExpired); ok {
132
133
134 return nil, ErrDecodeFailed{"root.json", err}
135 }
136 return nil, err
137 }
138
139
140 c.getLocalMeta()
141
142
143 timestampJSON, err := c.downloadMetaUnsafe("timestamp.json", defaultTimestampDownloadLimit)
144 if err != nil {
145 return nil, err
146 }
147
148
149 snapshotMeta, sameTimestampVersion, err := c.decodeTimestamp(timestampJSON)
150 if sameTimestampVersion {
151
152
153 return c.targets, nil
154 }
155
156 if err != nil {
157 return nil, err
158 }
159
160 if err := c.local.SetMeta("timestamp.json", timestampJSON); err != nil {
161 return nil, err
162 }
163
164
165
166 snapshotJSON, err := c.downloadMetaFromTimestamp("snapshot.json", snapshotMeta)
167 if err != nil {
168 return nil, err
169 }
170
171
172 snapshotMetas, err := c.decodeSnapshot(snapshotJSON)
173 if err != nil {
174 return nil, err
175 }
176
177 if err := c.local.SetMeta("snapshot.json", snapshotJSON); err != nil {
178 return nil, err
179 }
180
181
182
183 var updatedTargets data.TargetFiles
184 targetsMeta := snapshotMetas["targets.json"]
185 if !c.hasMetaFromSnapshot("targets.json", targetsMeta) {
186
187
188 targetsJSON, err := c.downloadMetaFromSnapshot("targets.json", targetsMeta)
189 if err != nil {
190 return nil, err
191 }
192
193 updatedTargets, err = c.decodeTargets(targetsJSON)
194 if err != nil {
195 return nil, err
196 }
197
198 if err := c.local.SetMeta("targets.json", targetsJSON); err != nil {
199 return nil, err
200 }
201 }
202
203 return updatedTargets, nil
204 }
205
206 func (c *Client) UpdateRoots() error {
207
208
209
210
211 if err := c.loadAndVerifyLocalRootMeta( true); err != nil {
212 return err
213 }
214 m, err := c.local.GetMeta()
215 if err != nil {
216 return err
217 }
218
219 type KeyInfo struct {
220 KeyIDs map[string]bool
221 Threshold int
222 }
223
224
225
226 getKeyInfo := func(role string) KeyInfo {
227 keyIDs := make(map[string]bool)
228 for k := range c.db.GetRole(role).KeyIDs {
229 keyIDs[k] = true
230 }
231 return KeyInfo{keyIDs, c.db.GetRole(role).Threshold}
232 }
233
234
235
236
237
238
239
240
241 nonRootKeyInfo := map[string]KeyInfo{"timestamp": {}, "snapshot": {}, "targets": {}}
242 for k := range nonRootKeyInfo {
243 nonRootKeyInfo[k] = getKeyInfo(k)
244 }
245
246
247
248 consistentSnapshot := c.consistentSnapshot
249 c.consistentSnapshot = true
250
251 nRootMetadata := m["root.json"]
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266 for i := 0; i < c.MaxRootRotations; i++ {
267
268
269 nPlusOne := c.rootVer + 1
270 nPlusOneRootPath := util.VersionedPath("root.json", nPlusOne)
271 nPlusOneRootMetadata, err := c.downloadMetaUnsafe(nPlusOneRootPath, defaultRootDownloadLimit)
272
273 if err != nil {
274 if _, ok := err.(ErrMissingRemoteMetadata); ok {
275
276 break
277 }
278 return err
279 }
280
281
282
283 nPlusOneRootMetadataSigned, err := c.verifyRoot(nRootMetadata, nPlusOneRootMetadata)
284 if err != nil {
285 return err
286 }
287
288
289 if _, err := c.verifyRoot(nPlusOneRootMetadata, nPlusOneRootMetadata); err != nil {
290
291
292
293 return err
294 }
295
296
297 if nPlusOneRootMetadataSigned.Version != nPlusOne {
298 return verify.ErrWrongVersion{
299 Given: nPlusOneRootMetadataSigned.Version,
300 Expected: nPlusOne,
301 }
302 }
303
304
305 c.rootVer = nPlusOneRootMetadataSigned.Version
306
307
308 consistentSnapshot = nPlusOneRootMetadataSigned.ConsistentSnapshot
309
310
311 if err := c.local.SetMeta("root.json", nPlusOneRootMetadata); err != nil {
312 return err
313 }
314 nRootMetadata = nPlusOneRootMetadata
315
316
317 }
318
319
320
321 if err := c.loadAndVerifyLocalRootMeta( false); err != nil {
322 return err
323 }
324
325 countDeleted := func(s1 map[string]bool, s2 map[string]bool) int {
326 c := 0
327 for k := range s1 {
328 if _, ok := s2[k]; !ok {
329 c++
330 }
331 }
332 return c
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346 for topLevelRolename := range nonRootKeyInfo {
347
348 ki := getKeyInfo(topLevelRolename)
349 if countDeleted(nonRootKeyInfo[topLevelRolename].KeyIDs, ki.KeyIDs) >= nonRootKeyInfo[topLevelRolename].Threshold {
350 deleteMeta := map[string][]string{
351 "timestamp": {"timestamp.json"},
352 "snapshot": {"timestamp.json", "snapshot.json"},
353 "targets": {"snapshot.json", "targets.json"},
354 }
355
356 for _, r := range deleteMeta[topLevelRolename] {
357 c.local.DeleteMeta(r)
358 }
359 }
360 }
361
362
363 c.consistentSnapshot = consistentSnapshot
364 return nil
365 }
366
367
368
369
370
371
372
373
374 func (c *Client) getLocalMeta() error {
375 var retErr error
376 loadFailed := false
377
378
379
380 c.localMeta = make(map[string]json.RawMessage)
381
382
383 if err := c.loadAndVerifyLocalRootMeta( false); err != nil {
384 return err
385 }
386
387
388 meta, err := c.local.GetMeta()
389 if err != nil {
390 return nil
391 }
392
393
394 if timestampJSON, ok := meta["timestamp.json"]; ok {
395 timestamp := &data.Timestamp{}
396 if err := c.db.UnmarshalTrusted(timestampJSON, timestamp, "timestamp"); err != nil {
397 loadFailed = true
398 retErr = err
399 } else {
400 c.localMeta["timestamp.json"] = meta["timestamp.json"]
401 c.timestampVer = timestamp.Version
402 }
403 }
404
405 snapshot := &data.Snapshot{}
406 if snapshotJSON, ok := meta["snapshot.json"]; ok {
407 if err := c.db.UnmarshalTrusted(snapshotJSON, snapshot, "snapshot"); err != nil {
408 loadFailed = true
409 retErr = err
410 } else {
411 c.localMeta["snapshot.json"] = meta["snapshot.json"]
412 c.snapshotVer = snapshot.Version
413 }
414 }
415
416 if targetsJSON, ok := meta["targets.json"]; ok {
417 targets := &data.Targets{}
418 if err := c.db.UnmarshalTrusted(targetsJSON, targets, "targets"); err != nil {
419 loadFailed = true
420 retErr = err
421 } else {
422 c.localMeta["targets.json"] = meta["targets.json"]
423 c.targetsVer = targets.Version
424
425
426 c.loadTargets(targets.Targets)
427 }
428 }
429
430 if loadFailed {
431
432
433 return retErr
434 }
435
436
437 verifiedDelegatedTargets := make(map[string]bool)
438 for fileName := range meta {
439 if !verifiedDelegatedTargets[fileName] && roles.IsDelegatedTargetsManifest(fileName) {
440 if delegationPath, err := c.getDelegationPathFromRaw(snapshot, meta[fileName]); err != nil {
441 loadFailed = true
442 retErr = err
443 } else {
444
445
446 for _, key := range delegationPath {
447 fileName := fmt.Sprintf("%s.json", key)
448 verifiedDelegatedTargets[fileName] = true
449 }
450 }
451 }
452 }
453
454 for fileName := range verifiedDelegatedTargets {
455 c.localMeta[fileName] = meta[fileName]
456 }
457
458 if loadFailed {
459
460 return retErr
461 }
462 return nil
463 }
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484 func (c *Client) getDelegationPathFromRaw(snapshot *data.Snapshot, delegatedTargetsJSON json.RawMessage) ([]string, error) {
485
486
487
488 s := &data.Signed{}
489 if err := json.Unmarshal(delegatedTargetsJSON, s); err != nil {
490 return nil, err
491 }
492 targets := &data.Targets{}
493 if err := json.Unmarshal(s.Signed, targets); err != nil {
494 return nil, err
495 }
496 for targetPath := range targets.Targets {
497
498 _, resp, err := c.getTargetFileMetaDelegationPath(targetPath, snapshot)
499
500
501
502 if errors.As(err, &ErrMissingRemoteMetadata{}) {
503
504
505
506
507 return nil, nil
508 }
509 if errors.As(err, &ErrUnknownTarget{}) {
510
511
512
513
514 continue
515 }
516 return resp, err
517 }
518 return nil, nil
519 }
520
521
522
523
524 func (c *Client) loadAndVerifyLocalRootMeta(ignoreExpiredCheck bool) error {
525 meta, err := c.local.GetMeta()
526 if err != nil {
527 return err
528 }
529 rootJSON, ok := meta["root.json"]
530 if !ok {
531 return ErrNoRootKeys
532 }
533 return c.loadAndVerifyRootMeta(rootJSON, ignoreExpiredCheck)
534 }
535
536
537
538 func (c *Client) loadAndVerifyRootMeta(rootJSON []byte, ignoreExpiredCheck bool) error {
539
540
541 s := &data.Signed{}
542 if err := json.Unmarshal(rootJSON, s); err != nil {
543 return err
544 }
545 root := &data.Root{}
546 if err := json.Unmarshal(s.Signed, root); err != nil {
547 return err
548 }
549 ndb := verify.NewDB()
550 for id, k := range root.Keys {
551 if err := ndb.AddKey(id, k); err != nil {
552 return err
553 }
554 }
555 for name, role := range root.Roles {
556 if err := ndb.AddRole(name, role); err != nil {
557 return err
558 }
559 }
560
561 if ignoreExpiredCheck {
562 if err := ndb.VerifyIgnoreExpiredCheck(s, "root", 0); err != nil {
563 return err
564 }
565 } else {
566 if err := ndb.Verify(s, "root", 0); err != nil {
567 return err
568 }
569 }
570 c.consistentSnapshot = root.ConsistentSnapshot
571 c.rootVer = root.Version
572 c.db = ndb
573 return nil
574 }
575
576
577
578 func (c *Client) verifyRoot(aJSON []byte, bJSON []byte) (*data.Root, error) {
579 aSigned := &data.Signed{}
580 if err := json.Unmarshal(aJSON, aSigned); err != nil {
581 return nil, err
582 }
583 aRoot := &data.Root{}
584 if err := json.Unmarshal(aSigned.Signed, aRoot); err != nil {
585 return nil, err
586 }
587
588 bSigned := &data.Signed{}
589 if err := json.Unmarshal(bJSON, bSigned); err != nil {
590 return nil, err
591 }
592 bRoot := &data.Root{}
593 if err := json.Unmarshal(bSigned.Signed, bRoot); err != nil {
594 return nil, err
595 }
596
597 ndb := verify.NewDB()
598 for id, k := range aRoot.Keys {
599 if err := ndb.AddKey(id, k); err != nil {
600 return nil, err
601 }
602 }
603 for name, role := range aRoot.Roles {
604 if err := ndb.AddRole(name, role); err != nil {
605 return nil, err
606 }
607 }
608
609 if err := ndb.VerifySignatures(bSigned, "root"); err != nil {
610 return nil, err
611 }
612 return bRoot, nil
613 }
614
615
616
617
618 func (c *Client) loadTargets(targets data.TargetFiles) {
619 c.targets = make(data.TargetFiles)
620 for name, meta := range targets {
621 c.targets[name] = meta
622 c.targets[util.NormalizeTarget(name)] = meta
623 }
624 }
625
626
627
628
629 func (c *Client) downloadMetaUnsafe(name string, maxMetaSize int64) ([]byte, error) {
630 r, size, err := c.remote.GetMeta(name)
631 if err != nil {
632 if IsNotFound(err) {
633 return nil, ErrMissingRemoteMetadata{name}
634 }
635 return nil, ErrDownloadFailed{name, err}
636 }
637 defer r.Close()
638
639
640 if size > maxMetaSize {
641 return nil, ErrMetaTooLarge{name, size, maxMetaSize}
642 }
643
644
645
646
647 return io.ReadAll(io.LimitReader(r, maxMetaSize))
648 }
649
650
651
652 type remoteGetFunc func(string) (io.ReadCloser, int64, error)
653
654
655 func (c *Client) downloadHashed(file string, get remoteGetFunc, hashes data.Hashes) (io.ReadCloser, int64, error) {
656
657
658 for _, path := range util.HashedPaths(file, hashes) {
659 r, size, err := get(path)
660 if err != nil {
661 if IsNotFound(err) {
662 continue
663 }
664 return nil, 0, err
665 }
666 return r, size, nil
667 }
668 return nil, 0, ErrNotFound{file}
669 }
670
671
672
673 func (c *Client) downloadTarget(file string, get remoteGetFunc, hashes data.Hashes) (io.ReadCloser, int64, error) {
674 if c.consistentSnapshot {
675 return c.downloadHashed(file, get, hashes)
676 } else {
677 return get(file)
678 }
679 }
680
681
682
683 func (c *Client) downloadMeta(name string, version int64, m data.FileMeta) ([]byte, error) {
684 r, size, err := func() (io.ReadCloser, int64, error) {
685 if c.consistentSnapshot {
686 path := util.VersionedPath(name, version)
687 r, size, err := c.remote.GetMeta(path)
688 if err == nil {
689 return r, size, nil
690 }
691
692 return nil, 0, err
693 } else {
694 return c.remote.GetMeta(name)
695 }
696 }()
697 if err != nil {
698 if IsNotFound(err) {
699 return nil, ErrMissingRemoteMetadata{name}
700 }
701 return nil, err
702 }
703 defer r.Close()
704
705
706 var stream io.Reader
707 if m.Length != 0 {
708 if size >= 0 && size != m.Length {
709 return nil, ErrWrongSize{name, size, m.Length}
710 }
711
712
713 stream = io.LimitReader(r, m.Length)
714 } else {
715 stream = r
716 }
717
718 return io.ReadAll(stream)
719 }
720
721 func (c *Client) downloadMetaFromSnapshot(name string, m data.SnapshotFileMeta) ([]byte, error) {
722 b, err := c.downloadMeta(name, m.Version, data.FileMeta{Length: m.Length, Hashes: m.Hashes})
723 if err != nil {
724 return nil, err
725 }
726
727
728 if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
729 return nil, ErrDownloadFailed{name, err}
730 }
731
732 meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
733 if err != nil {
734 return nil, err
735 }
736
737
738 if err := util.VersionEqual(meta.Version, m.Version); err != nil {
739 return nil, ErrDownloadFailed{name, err}
740 }
741
742 return b, nil
743 }
744
745 func (c *Client) downloadMetaFromTimestamp(name string, m data.TimestampFileMeta) ([]byte, error) {
746 b, err := c.downloadMeta(name, m.Version, data.FileMeta{Length: m.Length, Hashes: m.Hashes})
747 if err != nil {
748 return nil, err
749 }
750
751
752 if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
753 return nil, ErrDownloadFailed{name, err}
754 }
755
756 meta, err := util.GenerateTimestampFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
757 if err != nil {
758 return nil, err
759 }
760
761
762 if err := util.VersionEqual(meta.Version, m.Version); err != nil {
763 return nil, ErrDownloadFailed{name, err}
764 }
765
766 return b, nil
767 }
768
769
770
771 func (c *Client) decodeSnapshot(b json.RawMessage) (data.SnapshotFiles, error) {
772 snapshot := &data.Snapshot{}
773
774 if err := c.db.Unmarshal(b, snapshot, "snapshot", c.snapshotVer); err != nil {
775 return data.SnapshotFiles{}, ErrDecodeFailed{"snapshot.json", err}
776 }
777
778
779 if snapshot.Meta["targets.json"].Version < c.targetsVer {
780 return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: snapshot.Meta["targets.json"].Version, Current: c.targetsVer}
781 }
782
783
784
785
786 if snapshotJSON, ok := c.localMeta["snapshot.json"]; ok {
787 currentSnapshot := &data.Snapshot{}
788 if err := c.db.UnmarshalTrusted(snapshotJSON, currentSnapshot, "snapshot"); err != nil {
789 return data.SnapshotFiles{}, err
790 }
791
792 for path, local := range currentSnapshot.Meta {
793 if newMeta, ok := snapshot.Meta[path]; ok {
794
795 if newMeta.Version < local.Version {
796 return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: newMeta.Version, Current: local.Version}
797 }
798 } else {
799
800 return data.SnapshotFiles{}, verify.ErrMissingTargetFile
801 }
802 }
803 }
804
805
806
807 c.targetsVer = snapshot.Meta["targets.json"].Version
808 return snapshot.Meta, nil
809 }
810
811
812
813 func (c *Client) decodeTargets(b json.RawMessage) (data.TargetFiles, error) {
814 targets := &data.Targets{}
815
816 if err := c.db.Unmarshal(b, targets, "targets", c.targetsVer); err != nil {
817 return nil, ErrDecodeFailed{"targets.json", err}
818 }
819
820 updatedTargets := make(data.TargetFiles)
821 for path, meta := range targets.Targets {
822 if local, ok := c.targets[path]; ok {
823 if err := util.TargetFileMetaEqual(local, meta); err == nil {
824 continue
825 }
826 }
827 updatedTargets[path] = meta
828 }
829
830
831
832 c.loadTargets(targets.Targets)
833 return updatedTargets, nil
834 }
835
836
837
838
839
840 func (c *Client) decodeTimestamp(b json.RawMessage) (data.TimestampFileMeta, bool, error) {
841 timestamp := &data.Timestamp{}
842
843 if err := c.db.Unmarshal(b, timestamp, "timestamp", c.timestampVer); err != nil {
844 return data.TimestampFileMeta{}, false, ErrDecodeFailed{"timestamp.json", err}
845 }
846
847
848
849 if timestamp.Version == c.timestampVer {
850 return data.TimestampFileMeta{}, true, nil
851 }
852
853
854 if timestamp.Meta["snapshot.json"].Version < c.snapshotVer {
855 return data.TimestampFileMeta{}, false, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
856 }
857
858
859 c.timestampVer = timestamp.Version
860 c.snapshotVer = timestamp.Meta["snapshot.json"].Version
861 return timestamp.Meta["snapshot.json"], false, nil
862 }
863
864
865 func (c *Client) hasMetaFromSnapshot(name string, m data.SnapshotFileMeta) bool {
866 _, ok := c.localMetaFromSnapshot(name, m)
867 return ok
868 }
869
870
871 func (c *Client) localMetaFromSnapshot(name string, m data.SnapshotFileMeta) (json.RawMessage, bool) {
872 b, ok := c.localMeta[name]
873 if !ok {
874 return nil, false
875 }
876 meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
877 if err != nil {
878 return nil, false
879 }
880 err = util.SnapshotFileMetaEqual(meta, m)
881 return b, err == nil
882 }
883
884 type Destination interface {
885 io.Writer
886 Delete() error
887 }
888
889
890
891
892
893
894
895
896
897
898
899
900 func (c *Client) Download(name string, dest Destination) (err error) {
901
902 defer func() {
903 if err != nil {
904 dest.Delete()
905 }
906 }()
907
908
909 if c.targets == nil {
910 if err := c.getLocalMeta(); err != nil {
911 return err
912 }
913 }
914
915 normalizedName := util.NormalizeTarget(name)
916 localMeta, ok := c.targets[normalizedName]
917 if !ok {
918
919 localMeta, err = c.getTargetFileMeta(normalizedName)
920 if err != nil {
921 return err
922 }
923 }
924
925
926 r, size, err := c.downloadTarget(normalizedName, c.remote.GetTarget, localMeta.Hashes)
927 if err != nil {
928 return err
929 }
930 defer r.Close()
931
932
933 if size >= 0 && size != localMeta.Length {
934 return ErrWrongSize{name, size, localMeta.Length}
935 }
936
937
938 stream := io.LimitReader(r, localMeta.Length)
939
940
941 actual, err := util.GenerateTargetFileMeta(io.TeeReader(stream, dest), localMeta.HashAlgorithms()...)
942 if err != nil {
943 return ErrDownloadFailed{name, err}
944 }
945
946
947 if err := util.TargetFileMetaEqual(actual, localMeta); err != nil {
948 if e, ok := err.(util.ErrWrongLength); ok {
949 return ErrWrongSize{name, e.Actual, e.Expected}
950 }
951 return ErrDownloadFailed{name, err}
952 }
953
954 return nil
955 }
956
957 func (c *Client) VerifyDigest(digest string, digestAlg string, length int64, path string) error {
958 localMeta, ok := c.targets[path]
959 if !ok {
960 return ErrUnknownTarget{Name: path, SnapshotVersion: c.snapshotVer}
961 }
962
963 actual := data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)}
964 var err error
965 actual.Hashes[digestAlg], err = hex.DecodeString(digest)
966 if err != nil {
967 return err
968 }
969
970 if err := util.TargetFileMetaEqual(data.TargetFileMeta{FileMeta: actual}, localMeta); err != nil {
971 if e, ok := err.(util.ErrWrongLength); ok {
972 return ErrWrongSize{path, e.Actual, e.Expected}
973 }
974 return ErrDownloadFailed{path, err}
975 }
976
977 return nil
978 }
979
980
981
982
983 func (c *Client) Target(name string) (data.TargetFileMeta, error) {
984 target, err := c.getTargetFileMeta(util.NormalizeTarget(name))
985 if err == nil {
986 return target, nil
987 }
988
989 if _, ok := err.(ErrUnknownTarget); ok {
990 return data.TargetFileMeta{}, ErrNotFound{name}
991 }
992
993 return data.TargetFileMeta{}, err
994 }
995
996
997 func (c *Client) Targets() (data.TargetFiles, error) {
998
999 if c.targets == nil {
1000 if err := c.getLocalMeta(); err != nil {
1001 return nil, err
1002 }
1003 }
1004 return c.targets, nil
1005 }
1006
View as plain text