// downloadMeta downloads top-level metadata from remote storage and verifies // it using the given file metadata. func (c *Client) downloadMeta(name string, m data.FileMeta) ([]byte, error) { r, size, err := c.download(name, c.remote.GetMeta, m.Hashes) if err != nil { if IsNotFound(err) { return nil, ErrMissingRemoteMetadata{name} } return nil, err } defer r.Close() // return ErrWrongSize if the reported size is known and incorrect if size >= 0 && size != m.Length { return nil, ErrWrongSize{name, size, m.Length} } // wrap the data in a LimitReader so we download at most m.Length bytes stream := io.LimitReader(r, m.Length) // read the data, simultaneously writing it to buf and generating metadata var buf bytes.Buffer meta, err := util.GenerateFileMeta(io.TeeReader(stream, &buf), m.HashAlgorithms()...) if err != nil { return nil, err } if err := util.FileMetaEqual(meta, m); err != nil { return nil, ErrDownloadFailed{name, err} } return buf.Bytes(), nil }
// Download downloads the given target file from remote storage into dest. // // dest will be deleted and an error returned in the following situations: // // * The target does not exist in the local targets.json // * The target does not exist in remote storage // * Metadata cannot be generated for the downloaded data // * Generated metadata does not match local metadata for the given file func (c *Client) Download(name string, dest Destination) (err error) { // delete dest if there is an error defer func() { if err != nil { dest.Delete() } }() // populate c.targets from local storage if not set if c.targets == nil { if err := c.getLocalMeta(); err != nil { return err } } // return ErrUnknownTarget if the file is not in the local targets.json normalizedName := util.NormalizeTarget(name) localMeta, ok := c.targets[normalizedName] if !ok { return ErrUnknownTarget{name} } // get the data from remote storage r, size, err := c.download(normalizedName, c.remote.GetTarget, localMeta.Hashes) if err != nil { return err } defer r.Close() // return ErrWrongSize if the reported size is known and incorrect if size >= 0 && size != localMeta.Length { return ErrWrongSize{name, size, localMeta.Length} } // wrap the data in a LimitReader so we download at most localMeta.Length bytes stream := io.LimitReader(r, localMeta.Length) // read the data, simultaneously writing it to dest and generating metadata actual, err := util.GenerateFileMeta(io.TeeReader(stream, dest), localMeta.HashAlgorithms()...) if err != nil { return ErrDownloadFailed{name, err} } // check the data has the correct length and hashes if err := util.FileMetaEqual(actual, localMeta); err != nil { if err == util.ErrWrongLength { return ErrWrongSize{name, actual.Length, localMeta.Length} } return ErrDownloadFailed{name, err} } return nil }
// hasMeta checks whether local metadata has the given file meta func (c *Client) hasMeta(name string, m data.FileMeta) bool { b, ok := c.localMeta[name] if !ok { return false } meta, err := util.GenerateFileMeta(bytes.NewReader(b), m.HashAlgorithms()...) if err != nil { return false } err = util.FileMetaEqual(meta, m) return err == nil }
func (InteropSuite) TestGoClientPythonGenerated(c *C) { // start file server cwd, err := os.Getwd() c.Assert(err, IsNil) testDataDir := filepath.Join(cwd, "testdata") addr, cleanup := startFileServer(c, testDataDir) defer cleanup() for _, dir := range []string{"with-consistent-snapshot", "without-consistent-snapshot"} { remote, err := HTTPRemoteStore( fmt.Sprintf("http://%s/%s/repository", addr, dir), &HTTPRemoteOptions{MetadataPath: "metadata", TargetsPath: "targets"}, ) c.Assert(err, IsNil) // initiate a client with the root keys f, err := os.Open(filepath.Join("testdata", dir, "keystore", "root_key.pub")) c.Assert(err, IsNil) key := &data.Key{} c.Assert(json.NewDecoder(f).Decode(key), IsNil) c.Assert(key.Type, Equals, "ed25519") c.Assert(key.Value.Public, HasLen, ed25519.PublicKeySize) client := NewClient(MemoryLocalStore(), remote) c.Assert(client.Init([]*data.Key{key}, 1), IsNil) // check update returns the correct updated targets files, err := client.Update() c.Assert(err, IsNil) c.Assert(files, HasLen, len(pythonTargets)) for name, data := range pythonTargets { file, ok := files[name] if !ok { c.Fatalf("expected updated targets to contain %s", name) } meta, err := util.GenerateFileMeta(bytes.NewReader(data), file.HashAlgorithms()...) c.Assert(err, IsNil) c.Assert(util.FileMetaEqual(file, meta), IsNil) } // download the files and check they have the correct content for name, data := range pythonTargets { var dest testDestination c.Assert(client.Download(name, &dest), IsNil) c.Assert(dest.deleted, Equals, false) c.Assert(dest.String(), Equals, string(data)) } } }
func assertFiles(c *C, files data.Files, names []string) { c.Assert(files, HasLen, len(names)) for _, name := range names { target, ok := targetFiles[name] if !ok { c.Fatalf("unknown target %s", name) } file, ok := files[name] if !ok { c.Fatalf("expected files to contain %s", name) } meta, err := util.GenerateFileMeta(bytes.NewReader(target), file.HashAlgorithms()...) c.Assert(err, IsNil) c.Assert(util.FileMetaEqual(file, meta), IsNil) } }
// decodeTargets decodes and verifies targets metadata, sets c.targets and // returns updated targets. func (c *Client) decodeTargets(b json.RawMessage) (data.Files, error) { targets := &data.Targets{} if err := signed.Unmarshal(b, targets, "targets", c.targetsVer, c.db); err != nil { return nil, ErrDecodeFailed{"targets.json", err} } updatedTargets := make(data.Files) for path, meta := range targets.Targets { if local, ok := c.targets[path]; ok { if err := util.FileMetaEqual(local, meta); err == nil { continue } } updatedTargets[path] = meta } c.targetsVer = targets.Version c.targets = targets.Targets return updatedTargets, nil }