github.com/anishathalye/periscope@v0.3.5/internal/periscope/file.go (about)

     1  package periscope
     2  
     3  import (
     4  	"github.com/anishathalye/periscope/internal/herror"
     5  
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"golang.org/x/crypto/blake2b"
    13  )
    14  
    15  const initialChunkSize = 4 * 1024
    16  const readChunkSize = 1024 * 1024
    17  const ShortHashSize = 8
    18  const HashSize = blake2b.Size256
    19  
    20  func hashToArray(hash []byte) [HashSize]byte {
    21  	var res [HashSize]byte
    22  	copy(res[:], hash)
    23  	return res
    24  }
    25  
    26  func shortHashToArray(hash []byte) [ShortHashSize]byte {
    27  	var res [ShortHashSize]byte
    28  	copy(res[:], hash)
    29  	return res
    30  }
    31  
    32  func (ps *Periscope) hashPartial(path string, key []byte) ([]byte, error) {
    33  	buf := make([]byte, initialChunkSize)
    34  	h, err := blake2b.New256(key)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	f, err := ps.fs.Open(path)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	defer f.Close()
    43  
    44  	n, err := f.Read(buf)
    45  	if err != nil && err != io.EOF {
    46  		return nil, err
    47  	}
    48  	h.Write(buf[:n])
    49  	return h.Sum(nil)[:ShortHashSize], nil
    50  }
    51  
    52  // a simpler hashFile that hashes the full file
    53  // purposefully avoiding code reuse with the above
    54  func (ps *Periscope) hashFile(path string) ([]byte, error) {
    55  	f, err := ps.fs.Open(path)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	defer f.Close()
    60  	h, err := blake2b.New256(nil)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	buf := make([]byte, readChunkSize)
    65  	if _, err := io.CopyBuffer(h, f, buf); err != nil {
    66  		return nil, err
    67  	}
    68  	return h.Sum(nil), nil
    69  }
    70  
    71  func relPath(absDirectory, absPath string) string {
    72  	relPath, err := filepath.Rel(absDirectory, absPath)
    73  	if err != nil || len(relPath) > len(absPath) {
    74  		return absPath
    75  	}
    76  	return relPath
    77  }
    78  
    79  func relFrom(relDirectory, absPath string) string {
    80  	absDirectory, err := filepath.Abs(relDirectory)
    81  	if err != nil {
    82  		return absPath
    83  	}
    84  	relPath, err := filepath.Rel(absDirectory, absPath)
    85  	if err != nil {
    86  		return absPath
    87  	}
    88  	path := filepath.Join(relDirectory, relPath)
    89  	if len(path) <= len(absPath) {
    90  		return path
    91  	}
    92  	return absPath
    93  }
    94  
    95  func (ps *Periscope) checkSymlinks(cleanAbsPath string) (hasSymlinks bool, realAbsPath string, err error) {
    96  	if !ps.realFs {
    97  		// filepath.EvalSymlinks() won't be a sensible thing to do when
    98  		// testing with afero's in-memory filesystem
    99  		return false, cleanAbsPath, nil
   100  	}
   101  	resolved, err := filepath.EvalSymlinks(cleanAbsPath)
   102  	if err != nil {
   103  		return false, "", err
   104  	}
   105  	return (resolved != cleanAbsPath), resolved, nil
   106  }
   107  
   108  func (ps *Periscope) checkFile(path string, mustBeRegularFile, mustBeDirectory bool, action string, quiet, fatal bool) (realAbsPath string, info os.FileInfo, herr herror.Interface) {
   109  	checkFileError := func(format string, a ...interface{}) herror.Interface {
   110  		if !fatal {
   111  			if !quiet {
   112  				fmt.Fprintf(ps.errStream, format, a...)
   113  				fmt.Fprintf(ps.errStream, "\n")
   114  			}
   115  			return herror.Silent()
   116  		}
   117  		return herror.UserF(nil, format, a...)
   118  	}
   119  	// check that it exists
   120  	info, err := ps.fs.Stat(path)
   121  	if err != nil {
   122  		if os.IsNotExist(err) {
   123  			return "", nil, checkFileError("cannot %s '%s': no such file or directory", action, path)
   124  		}
   125  		if os.IsPermission(err) {
   126  			return "", nil, checkFileError("cannot %s '%s': permission denied", action, path)
   127  		}
   128  		// what else can go wrong here? one example is too many levels of symbolic links
   129  		return "", nil, checkFileError("cannot %s '%s': %s", action, path, err.Error())
   130  	}
   131  	// get an absolute path
   132  	absPath, err := filepath.Abs(path)
   133  	if err != nil {
   134  		// when can this happen?
   135  		return "", nil, herror.Internal(err, "")
   136  	}
   137  	// check whether there are any symbolic links involved
   138  	hasLinks, resolved, err := ps.checkSymlinks(absPath)
   139  	if err != nil {
   140  		// when can this happen? we've already checked that the file or
   141  		// directory exists
   142  		return "", nil, herror.Internal(err, "")
   143  	}
   144  	if hasLinks {
   145  		return "", nil, checkFileError("cannot %s '%s': path has symbolic links (use '%s' instead)", action, path, resolved)
   146  	}
   147  	// at this point, absPath == resolved
   148  	// check whether it's a regular file or directory
   149  	if !info.Mode().IsRegular() && !info.Mode().IsDir() {
   150  		return "", nil, checkFileError("cannot %s '%s': not a regular file or directory", action, path)
   151  	}
   152  	// check whether it's a regular file, if required
   153  	if mustBeRegularFile && !info.Mode().IsRegular() {
   154  		return "", nil, checkFileError("cannot %s '%s': not a regular file", action, path)
   155  	}
   156  	// check whether it's a directory, if required
   157  	if mustBeDirectory && !info.Mode().IsDir() {
   158  		return "", nil, checkFileError("cannot %s '%s': not a directory", action, path)
   159  	}
   160  	// all okay
   161  	return absPath, info, nil
   162  }
   163  
   164  func containedInAny(path string, dirs []string) bool {
   165  	for _, dir := range dirs {
   166  		if dir[len(dir)-1] != os.PathSeparator {
   167  			dir = dir + string(os.PathSeparator)
   168  		}
   169  		if strings.HasPrefix(path, dir) {
   170  			return true
   171  		}
   172  	}
   173  	return false
   174  }