github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/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  	// create a set of hasher objects tied together with a single writer to feed content into
    26  	hashers := make([]hash.Hash, len(hashes))
    27  	writers := make([]io.Writer, len(hashes))
    28  	for idx, hashObj := range hashes {
    29  		hashers[idx] = hashObj.New()
    30  		writers[idx] = hashers[idx]
    31  	}
    32  
    33  	size, err := io.Copy(io.MultiWriter(writers...), closer)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	if size == 0 {
    39  		return make([]file.Digest, 0), nil
    40  	}
    41  
    42  	result := make([]file.Digest, len(hashes))
    43  	// only capture digests when there is content. It is important to do this based on SIZE and not
    44  	// FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
    45  	// file type but a body is still allowed.
    46  	for idx, hasher := range hashers {
    47  		result[idx] = file.Digest{
    48  			Algorithm: CleanDigestAlgorithmName(hashes[idx].String()),
    49  			Value:     fmt.Sprintf("%+x", hasher.Sum(nil)),
    50  		}
    51  	}
    52  
    53  	return result, nil
    54  }
    55  
    56  func Hashers(names ...string) ([]crypto.Hash, error) {
    57  	hashByName := make(map[string]crypto.Hash)
    58  	for _, h := range supportedHashAlgorithms() {
    59  		hashByName[CleanDigestAlgorithmName(h.String())] = h
    60  	}
    61  
    62  	var hashers []crypto.Hash
    63  	for _, hashStr := range names {
    64  		hashObj, ok := hashByName[CleanDigestAlgorithmName(hashStr)]
    65  		if !ok {
    66  			return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr)
    67  		}
    68  		hashers = append(hashers, hashObj)
    69  	}
    70  	return hashers, nil
    71  }
    72  
    73  func CleanDigestAlgorithmName(name string) string {
    74  	lower := strings.ToLower(name)
    75  	return strings.ReplaceAll(lower, "-", "")
    76  }