croc/src/utils/utils.go

464 lines
10 KiB
Go
Raw Normal View History

2019-04-30 00:35:07 +02:00
package utils
import (
2022-07-06 15:16:02 +02:00
"archive/zip"
2019-04-30 00:35:07 +02:00
"bufio"
2019-05-03 05:28:31 +02:00
"bytes"
"compress/flate"
2019-04-30 00:35:07 +02:00
"crypto/md5"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
2019-04-30 00:35:07 +02:00
"fmt"
"io"
"log"
2019-04-30 04:50:01 +02:00
"math"
"math/big"
2019-05-03 05:28:31 +02:00
"net"
2019-04-30 00:35:07 +02:00
"net/http"
"os"
2022-02-03 17:34:03 +01:00
"path"
2022-07-06 15:16:02 +02:00
"path/filepath"
2019-04-30 00:35:07 +02:00
"strings"
2020-09-21 14:51:50 +02:00
"time"
2019-04-30 00:35:07 +02:00
2019-05-03 05:28:31 +02:00
"github.com/cespare/xxhash"
"github.com/kalafut/imohash"
2019-04-30 00:35:07 +02:00
"github.com/schollz/mnemonicode"
)
2023-03-15 10:03:38 +01:00
const NbPinNumbers = 4
const NbBytesWords = 4
2022-02-03 17:34:03 +01:00
// Get or create home directory
func GetConfigDir() (homedir string, err error) {
if envHomedir, isSet := os.LookupEnv("CROC_CONFIG_DIR"); isSet {
homedir = envHomedir
} else if xdgConfigHome, isSet := os.LookupEnv("XDG_CONFIG_HOME"); isSet {
homedir = path.Join(xdgConfigHome, "croc")
} else {
2024-03-30 23:21:42 +01:00
homedir, err = os.UserHomeDir()
if err != nil {
return
}
2022-02-03 17:34:03 +01:00
homedir = path.Join(homedir, ".config", "croc")
}
if _, err = os.Stat(homedir); os.IsNotExist(err) {
2022-12-05 20:21:04 +01:00
err = os.MkdirAll(homedir, 0o700)
2022-02-03 17:34:03 +01:00
}
return
}
2019-04-30 00:35:07 +02:00
// Exists reports whether the named file or directory exists.
func Exists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// GetInput returns the input with a given prompt
func GetInput(prompt string) string {
reader := bufio.NewReader(os.Stdin)
fmt.Fprintf(os.Stderr, "%s", prompt)
text, _ := reader.ReadString('\n')
return strings.TrimSpace(text)
}
// HashFile returns the hash of a file or, in case of a symlink, the
2021-04-21 00:32:05 +02:00
// SHA256 hash of its target. Takes an argument to specify the algorithm to use.
func HashFile(fname string, algorithm string) (hash256 []byte, err error) {
var fstats os.FileInfo
fstats, err = os.Lstat(fname)
if err != nil {
return nil, err
}
if fstats.Mode()&os.ModeSymlink != 0 {
var target string
target, err = os.Readlink(fname)
2022-01-06 10:44:43 +01:00
if err != nil {
return nil, err
}
return []byte(SHA256(target)), nil
}
2021-04-21 00:32:05 +02:00
switch algorithm {
case "imohash":
return IMOHashFile(fname)
case "md5":
return MD5HashFile(fname)
case "xxhash":
return XXHashFile(fname)
}
err = fmt.Errorf("unspecified algorithm")
return
2019-05-03 05:28:31 +02:00
}
2019-09-07 18:46:04 +02:00
// MD5HashFile returns MD5 hash
2019-05-03 05:28:31 +02:00
func MD5HashFile(fname string) (hash256 []byte, err error) {
2019-04-30 00:35:07 +02:00
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
h := md5.New()
if _, err = io.Copy(h, f); err != nil {
return
}
hash256 = h.Sum(nil)
return
}
2019-05-03 05:28:31 +02:00
// IMOHashFile returns imohash
func IMOHashFile(fname string) (hash []byte, err error) {
b, err := imohash.SumFile(fname)
hash = b[:]
return
}
2021-04-21 21:40:41 +02:00
var imofull = imohash.NewCustom(0, 0)
// IMOHashFileFull returns imohash of full file
func IMOHashFileFull(fname string) (hash []byte, err error) {
b, err := imofull.SumFile(fname)
hash = b[:]
return
}
2019-05-03 05:28:31 +02:00
// XXHashFile returns the xxhash of a file
func XXHashFile(fname string) (hash256 []byte, err error) {
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
h := xxhash.New()
if _, err = io.Copy(h, f); err != nil {
return
}
hash256 = h.Sum(nil)
return
}
2019-04-30 00:35:07 +02:00
// SHA256 returns sha256 sum
func SHA256(s string) string {
sha := sha256.New()
sha.Write([]byte(s))
return hex.EncodeToString(sha.Sum(nil))
2019-04-30 00:35:07 +02:00
}
2019-09-07 18:46:04 +02:00
// PublicIP returns public ip address
2019-04-30 00:35:07 +02:00
func PublicIP() (ip string, err error) {
resp, err := http.Get("https://canhazip.com")
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
bodyBytes, err := io.ReadAll(resp.Body)
2019-04-30 00:35:07 +02:00
if err != nil {
return "", err
}
ip = strings.TrimSpace(string(bodyBytes))
}
return
}
2019-09-07 18:46:04 +02:00
// LocalIP returns local ip address
2019-04-30 00:35:07 +02:00
func LocalIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}
func GenerateRandomPin() string {
s := ""
max := new(big.Int)
max.SetInt64(9)
2023-03-15 10:03:38 +01:00
for i := 0; i < NbPinNumbers; i++ {
v, err := rand.Int(rand.Reader, max)
if err != nil {
panic(err)
}
s += fmt.Sprintf("%d", v)
}
return s
}
// GetRandomName returns mnemonicoded random name
2019-04-30 00:35:07 +02:00
func GetRandomName() string {
2020-08-23 01:05:00 +02:00
var result []string
2023-03-15 10:03:38 +01:00
bs := make([]byte, NbBytesWords)
2019-04-30 00:35:07 +02:00
rand.Read(bs)
result = mnemonicode.EncodeWordList(result, bs)
return GenerateRandomPin() + "-" + strings.Join(result, "-")
2019-04-30 00:35:07 +02:00
}
2019-04-30 01:16:38 +02:00
2019-09-07 18:46:04 +02:00
// ByteCountDecimal converts bytes to human readable byte string
2019-04-30 01:16:38 +02:00
func ByteCountDecimal(b int64) string {
const unit = 1024
2019-04-30 01:16:38 +02:00
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}
2019-04-30 04:50:01 +02:00
// MissingChunks returns the positions of missing chunks.
// If file doesn't exist, it returns an empty chunk list (all chunks).
// If the file size is not the same as requested, it returns an empty chunk list (all chunks).
func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int64) {
2019-04-30 04:50:01 +02:00
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
2019-09-08 15:07:53 +02:00
fstat, err := os.Stat(fname)
2020-08-23 01:05:00 +02:00
if err != nil || fstat.Size() != fsize {
2019-09-08 15:07:53 +02:00
return
}
2019-04-30 04:50:01 +02:00
emptyBuffer := make([]byte, chunkSize)
chunkNum := 0
chunks := make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize))))
2019-04-30 04:50:01 +02:00
var currentLocation int64
for {
2019-04-30 16:29:02 +02:00
buffer := make([]byte, chunkSize)
2019-04-30 04:50:01 +02:00
bytesread, err := f.Read(buffer)
if err != nil {
break
}
if bytes.Equal(buffer[:bytesread], emptyBuffer[:bytesread]) {
chunks[chunkNum] = currentLocation
2019-04-30 16:29:02 +02:00
chunkNum++
2019-04-30 04:50:01 +02:00
}
currentLocation += int64(bytesread)
}
if chunkNum == 0 {
chunkRanges = []int64{}
2019-04-30 04:50:01 +02:00
} else {
chunks = chunks[:chunkNum]
chunkRanges = []int64{int64(chunkSize), chunks[0]}
curCount := 0
for i, chunk := range chunks {
if i == 0 {
continue
}
curCount++
if chunk-chunks[i-1] > int64(chunkSize) {
chunkRanges = append(chunkRanges, int64(curCount))
chunkRanges = append(chunkRanges, chunk)
curCount = 0
}
}
chunkRanges = append(chunkRanges, int64(curCount+1))
}
return
}
2019-09-07 18:46:04 +02:00
// ChunkRangesToChunks converts chunk ranges to list
func ChunkRangesToChunks(chunkRanges []int64) (chunks []int64) {
if len(chunkRanges) == 0 {
return
}
chunkSize := chunkRanges[0]
chunks = []int64{}
for i := 1; i < len(chunkRanges); i += 2 {
for j := int64(0); j < (chunkRanges[i+1]); j++ {
chunks = append(chunks, chunkRanges[i]+j*chunkSize)
}
2019-04-30 04:50:01 +02:00
}
return
}
2019-09-07 18:46:04 +02:00
// GetLocalIPs returns all local ips
func GetLocalIPs() (ips []string, err error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return
}
ips = []string{}
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ips = append(ips, ipnet.IP.String())
}
}
}
return
}
func RandomFileName() (fname string, err error) {
f, err := os.CreateTemp(".", "croc-stdin-")
if err != nil {
return
}
fname = f.Name()
2020-10-04 04:38:30 +02:00
_ = f.Close()
return
}
2020-09-21 14:51:50 +02:00
func FindOpenPorts(host string, portNumStart, numPorts int) (openPorts []int) {
openPorts = []int{}
for port := portNumStart; port-portNumStart < 200; port++ {
timeout := 100 * time.Millisecond
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, fmt.Sprint(port)), timeout)
if conn != nil {
conn.Close()
} else if err != nil {
openPorts = append(openPorts, port)
}
if len(openPorts) >= numPorts {
return
}
}
return
}
2020-10-05 17:30:45 +02:00
// local ip determination
// https://stackoverflow.com/questions/41240761/check-if-ip-address-is-in-private-network-space
var privateIPBlocks []*net.IPNet
func init() {
for _, cidr := range []string{
"127.0.0.0/8", // IPv4 loopback
"10.0.0.0/8", // RFC1918
"172.16.0.0/12", // RFC1918
"192.168.0.0/16", // RFC1918
"169.254.0.0/16", // RFC3927 link-local
"::1/128", // IPv6 loopback
"fe80::/10", // IPv6 link-local
"fc00::/7", // IPv6 unique local addr
} {
_, block, err := net.ParseCIDR(cidr)
if err != nil {
panic(fmt.Errorf("parse error on %q: %v", cidr, err))
}
privateIPBlocks = append(privateIPBlocks, block)
}
}
func IsLocalIP(ipaddress string) bool {
2023-03-20 17:16:07 +01:00
if strings.Contains(ipaddress, "127.0.0.1") {
2020-10-05 17:30:45 +02:00
return true
}
host, _, _ := net.SplitHostPort(ipaddress)
ip := net.ParseIP(host)
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return true
}
for _, block := range privateIPBlocks {
if block.Contains(ip) {
return true
}
}
return false
}
2022-07-06 15:16:02 +02:00
func ZipDirectory(destination string, source string) (err error) {
2022-12-05 20:21:04 +01:00
if _, err = os.Stat(destination); err == nil {
2022-07-06 15:16:02 +02:00
log.Fatalf("%s file already exists!\n", destination)
}
fmt.Fprintf(os.Stderr, "Zipping %s to %s\n", source, destination)
file, err := os.Create(destination)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
writer := zip.NewWriter(file)
// no compression because croc does its compression on the fly
writer.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
return flate.NewWriter(out, flate.NoCompression)
})
2022-07-06 15:16:02 +02:00
defer writer.Close()
err = filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Fatalln(err)
}
if info.Mode().IsRegular() {
f1, err := os.Open(path)
if err != nil {
log.Fatalln(err)
}
defer f1.Close()
2022-12-05 20:21:04 +01:00
zipPath := strings.ReplaceAll(path, source, strings.TrimSuffix(destination, ".zip"))
zipPath = filepath.ToSlash(zipPath)
2022-12-05 20:21:04 +01:00
w1, err := writer.Create(zipPath)
2022-07-06 15:16:02 +02:00
if err != nil {
log.Fatalln(err)
}
if _, err := io.Copy(w1, f1); err != nil {
log.Fatalln(err)
}
fmt.Fprintf(os.Stderr, "\r\033[2K")
2022-12-05 20:21:04 +01:00
fmt.Fprintf(os.Stderr, "\rAdding %s", zipPath)
2022-07-06 15:16:02 +02:00
}
return nil
})
if err != nil {
log.Fatalln(err)
}
2022-07-07 19:46:49 +02:00
fmt.Fprintf(os.Stderr, "\n")
2022-07-06 15:16:02 +02:00
return nil
}
2022-07-06 20:35:56 +02:00
func UnzipDirectory(destination string, source string) error {
archive, err := zip.OpenReader(source)
if err != nil {
log.Fatalln(err)
}
defer archive.Close()
for _, f := range archive.File {
filePath := filepath.Join(destination, f.Name)
fmt.Fprintf(os.Stderr, "\r\033[2K")
fmt.Fprintf(os.Stderr, "\rUnzipping file %s", filePath)
if f.FileInfo().IsDir() {
os.MkdirAll(filePath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
log.Fatalln(err)
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
log.Fatalln(err)
}
fileInArchive, err := f.Open()
if err != nil {
log.Fatalln(err)
}
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
log.Fatalln(err)
}
dstFile.Close()
fileInArchive.Close()
}
2022-07-07 19:46:49 +02:00
fmt.Fprintf(os.Stderr, "\n")
2022-07-06 20:35:56 +02:00
return nil
}