github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/internal/file/digest.go (about)

     1  package file
     2  
     3  import (
     4  	"crypto"
     5  	"fmt"
     6  	"hash"
     7  	"io"
     8  	"strings"
     9  
    10  	"github.com/anchore/syft/syft/file"
    11  )
    12  
    13  func supportedHashAlgorithms() []crypto.Hash {
    14  	return []crypto.Hash{
    15  		crypto.MD5,
    16  		crypto.SHA1,
    17  		crypto.SHA224,
    18  		crypto.SHA256,
    19  		crypto.SHA384,
    20  		crypto.SHA512,
    21  	}
    22  }
    23  
    24  func NewDigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]file.Digest, error) {
    25  	hashes = NormalizeHashes(hashes)
    26  	// create a set of hasher objects tied together with a single writer to feed content into
    27  	hashers := make([]hash.Hash, len(hashes))
    28  	writers := make([]io.Writer, len(hashes))
    29  	for idx, hashObj := range hashes {
    30  		hashers[idx] = hashObj.New()
    31  		writers[idx] = hashers[idx]
    32  	}
    33  
    34  	size, err := io.Copy(io.MultiWriter(writers...), closer)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	if size == 0 {
    40  		return make([]file.Digest, 0), nil
    41  	}
    42  
    43  	result := make([]file.Digest, len(hashes))
    44  	// only capture digests when there is content. It is important to do this based on SIZE and not
    45  	// FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
    46  	// file type but a body is still allowed.
    47  	for idx, hasher := range hashers {
    48  		result[idx] = file.Digest{
    49  			Algorithm: CleanDigestAlgorithmName(hashes[idx].String()),
    50  			Value:     fmt.Sprintf("%+x", hasher.Sum(nil)),
    51  		}
    52  	}
    53  
    54  	return result, nil
    55  }
    56  
    57  func Hashers(names ...string) ([]crypto.Hash, error) {
    58  	hashByName := make(map[string]crypto.Hash)
    59  	for _, h := range supportedHashAlgorithms() {
    60  		hashByName[CleanDigestAlgorithmName(h.String())] = h
    61  	}
    62  
    63  	var hashers []crypto.Hash
    64  	for _, hashStr := range names {
    65  		hashObj, ok := hashByName[CleanDigestAlgorithmName(hashStr)]
    66  		if !ok {
    67  			return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr)
    68  		}
    69  		hashers = append(hashers, hashObj)
    70  	}
    71  	return NormalizeHashes(hashers), nil
    72  }
    73  
    74  func CleanDigestAlgorithmName(name string) string {
    75  	lower := strings.ToLower(name)
    76  	return strings.ReplaceAll(lower, "-", "")
    77  }