1  
     2  
     3  
     4  
     5  
     6  
     7  
     8  package dirhash
     9  
    10  import (
    11  	"archive/zip"
    12  	"crypto/sha256"
    13  	"encoding/base64"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  	"slices"
    20  	"strings"
    21  )
    22  
    23  
    24  var DefaultHash Hash = Hash1
    25  
    26  
    27  
    28  
    29  type Hash func(files []string, open func(string) (io.ReadCloser, error)) (string, error)
    30  
    31  
    32  
    33  
    34  
    35  
    36  
    37  
    38  
    39  
    40  
    41  
    42  
    43  
    44  func Hash1(files []string, open func(string) (io.ReadCloser, error)) (string, error) {
    45  	h := sha256.New()
    46  	files = append([]string(nil), files...)
    47  	slices.Sort(files)
    48  	for _, file := range files {
    49  		if strings.Contains(file, "\n") {
    50  			return "", errors.New("dirhash: filenames with newlines are not supported")
    51  		}
    52  		r, err := open(file)
    53  		if err != nil {
    54  			return "", err
    55  		}
    56  		hf := sha256.New()
    57  		_, err = io.Copy(hf, r)
    58  		r.Close()
    59  		if err != nil {
    60  			return "", err
    61  		}
    62  		fmt.Fprintf(h, "%x  %s\n", hf.Sum(nil), file)
    63  	}
    64  	return "h1:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
    65  }
    66  
    67  
    68  
    69  
    70  func HashDir(dir, prefix string, hash Hash) (string, error) {
    71  	files, err := DirFiles(dir, prefix)
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  	osOpen := func(name string) (io.ReadCloser, error) {
    76  		return os.Open(filepath.Join(dir, strings.TrimPrefix(name, prefix)))
    77  	}
    78  	return hash(files, osOpen)
    79  }
    80  
    81  
    82  
    83  
    84  func DirFiles(dir, prefix string) ([]string, error) {
    85  	var files []string
    86  	dir = filepath.Clean(dir)
    87  	err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
    88  		if err != nil {
    89  			return err
    90  		}
    91  		if info.IsDir() {
    92  			return nil
    93  		} else if file == dir {
    94  			return fmt.Errorf("%s is not a directory", dir)
    95  		}
    96  
    97  		rel := file
    98  		if dir != "." {
    99  			rel = file[len(dir)+1:]
   100  		}
   101  		f := filepath.Join(prefix, rel)
   102  		files = append(files, filepath.ToSlash(f))
   103  		return nil
   104  	})
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	return files, nil
   109  }
   110  
   111  
   112  
   113  
   114  
   115  func HashZip(zipfile string, hash Hash) (string, error) {
   116  	z, err := zip.OpenReader(zipfile)
   117  	if err != nil {
   118  		return "", err
   119  	}
   120  	defer z.Close()
   121  	var files []string
   122  	zfiles := make(map[string]*zip.File)
   123  	for _, file := range z.File {
   124  		files = append(files, file.Name)
   125  		zfiles[file.Name] = file
   126  	}
   127  	zipOpen := func(name string) (io.ReadCloser, error) {
   128  		f := zfiles[name]
   129  		if f == nil {
   130  			return nil, fmt.Errorf("file %q not found in zip", name) 
   131  		}
   132  		return f.Open()
   133  	}
   134  	return hash(files, zipOpen)
   135  }
   136  
View as plain text