go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipkg/base/actions/url.go (about) 1 // Copyright 2023 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package actions 16 17 import ( 18 "context" 19 "crypto" 20 "errors" 21 "fmt" 22 "hash" 23 "io" 24 "net/http" 25 "os" 26 "path/filepath" 27 28 "go.chromium.org/luci/cipkg/core" 29 ) 30 31 // ActionURLFetchTransformer is the default transformer for 32 // core.ActionURLFetch. 33 func ActionURLFetchTransformer(a *core.ActionURLFetch, deps []Package) (*core.Derivation, error) { 34 return ReexecDerivation(a, false) 35 } 36 37 // ActionURLFetchExecutor is the default executor for core.ActionURLFetch. 38 func ActionURLFetchExecutor(ctx context.Context, a *core.ActionURLFetch, out string) (err error) { 39 req, err := http.NewRequestWithContext(ctx, http.MethodGet, a.Url, nil) 40 if err != nil { 41 return 42 } 43 44 joinErr := func(f func() error) { err = errors.Join(err, f()) } 45 46 resp, err := http.DefaultClient.Do(req) 47 if err != nil { 48 return 49 } 50 defer joinErr(resp.Body.Close) 51 52 f, err := os.Create(filepath.Join(out, "file")) 53 if err != nil { 54 return 55 } 56 defer joinErr(f.Close) 57 58 h, err := hashFromEnum(a.HashAlgorithm) 59 if err != nil { 60 return 61 } 62 63 if _, err = io.Copy(f, io.TeeReader(resp.Body, h)); err != nil { 64 return 65 } 66 67 if actual := fmt.Sprintf("%x", h.Sum(nil)); actual != a.HashValue { 68 return fmt.Errorf("hash mismatch: expected: %s, actual: %s", a.HashValue, actual) 69 } 70 return 71 } 72 73 func hashFromEnum(h core.HashAlgorithm) (hash.Hash, error) { 74 if h == core.HashAlgorithm_HASH_UNSPECIFIED { 75 return &emptyHash{}, nil 76 } 77 78 switch h { 79 case core.HashAlgorithm_HASH_MD5: 80 return crypto.MD5.New(), nil 81 case core.HashAlgorithm_HASH_SHA256: 82 return crypto.SHA256.New(), nil 83 } 84 85 return nil, fmt.Errorf("unknown hash algorithm: %s", h) 86 } 87 88 // If no hash algorithm is provided, we will use an empty hash to skip the 89 // check and assume the artifact from the url is always same. This is only 90 // used for legacy content. 91 type emptyHash struct{} 92 93 func (h *emptyHash) Write(p []byte) (int, error) { return len(p), nil } 94 func (h *emptyHash) Sum(b []byte) []byte { return []byte{} } 95 func (h *emptyHash) Reset() {} 96 func (h *emptyHash) Size() int { return 0 } 97 func (h *emptyHash) BlockSize() int { return 0 }