github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/utils/utils.go (about)

     1  package utils
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"net"
     7  	"net/url"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  	"time"
    12  	"unicode/utf8"
    13  
    14  	"golang.org/x/net/idna"
    15  )
    16  
    17  func init() {
    18  	// So that we do not generate the same IDs upon restart
    19  	rand.Seed(time.Now().UTC().UnixNano())
    20  }
    21  
    22  // RandomString returns a string of random alpha characters of the specified
    23  // length.
    24  func RandomString(n int) string {
    25  	const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    26  	b := make([]byte, n)
    27  	lenLetters := len(letters)
    28  	for i := 0; i < n; i++ {
    29  		b[i] = letters[rand.Intn(lenLetters)]
    30  	}
    31  	return string(b)
    32  }
    33  
    34  // RandomStringFast returns a random string containing printable ascii
    35  // characters: [0-9a-zA-Z_-]{n}. Each character encodes 6bits of entropy. To
    36  // avoid wasting entropy, it is better to create a string whose length is a
    37  // multiple of 10. For instance a 20 bytes string will encode 120 bits of
    38  // entropy.
    39  func RandomStringFast(rng *rand.Rand, n int) string {
    40  	// extract 10 letters — 60 bits of entropy — for each pseudo-random uint64
    41  	const K = 10
    42  	const L = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"
    43  	b := make([]byte, ((n+K-1)/K)*K)
    44  	for i := 0; i < n; i += K {
    45  		rn := rng.Uint64()
    46  		for j := 0; j < K; j++ {
    47  			b[i+j] = L[rn&0x3F]
    48  			rn >>= 6
    49  		}
    50  	}
    51  	return string(b[:n])
    52  }
    53  
    54  // IsInArray returns whether or not a string is in the given array of strings.
    55  func IsInArray(s string, a []string) bool {
    56  	for _, ss := range a {
    57  		if s == ss {
    58  			return true
    59  		}
    60  	}
    61  	return false
    62  }
    63  
    64  // StripPort extract the domain name from a domain:port string.
    65  func StripPort(domain string) string {
    66  	if strings.Contains(domain, ":") {
    67  		cleaned, _, err := net.SplitHostPort(domain)
    68  		if err != nil {
    69  			return domain
    70  		}
    71  		return cleaned
    72  	}
    73  	return domain
    74  }
    75  
    76  // CookieDomain removes the port and does IDNA encoding.
    77  func CookieDomain(domain string) string {
    78  	domain = StripPort(domain)
    79  	ascii, err := idna.ToASCII(domain)
    80  	if err != nil {
    81  		return domain
    82  	}
    83  	return ascii
    84  }
    85  
    86  // SplitTrimString slices s into all substrings a s separated by sep, like
    87  // strings.Split. In addition it will trim all those substrings and filter out
    88  // the empty ones.
    89  func SplitTrimString(s, sep string) []string {
    90  	if s == "" {
    91  		return []string{}
    92  	}
    93  	return TrimStrings(strings.Split(s, sep))
    94  }
    95  
    96  // TrimStrings trim all strings and filter out the empty ones.
    97  func TrimStrings(strs []string) []string {
    98  	filteredStrs := strs[:0]
    99  	for _, part := range strs {
   100  		part = strings.TrimSpace(part)
   101  		if part != "" {
   102  			filteredStrs = append(filteredStrs, part)
   103  		}
   104  	}
   105  	return filteredStrs
   106  }
   107  
   108  // UniqueStrings returns a filtered slice without string duplicates.
   109  func UniqueStrings(strs []string) []string {
   110  	filteredStrs := strs[:0]
   111  	for _, str1 := range strs {
   112  		found := false
   113  		for _, str2 := range filteredStrs {
   114  			if str1 == str2 {
   115  				found = true
   116  				break
   117  			}
   118  		}
   119  		if !found {
   120  			filteredStrs = append(filteredStrs, str1)
   121  		}
   122  	}
   123  	return filteredStrs
   124  }
   125  
   126  // FileExists returns whether or not the file exists on the current file
   127  // system.
   128  func FileExists(name string) (bool, error) {
   129  	infos, err := os.Stat(name)
   130  	if err != nil {
   131  		if os.IsNotExist(err) {
   132  			return false, nil
   133  		}
   134  		return false, err
   135  	}
   136  	if infos.IsDir() {
   137  		return false, fmt.Errorf("Path %s is a directory", name)
   138  	}
   139  	return true, nil
   140  }
   141  
   142  // DirExists returns whether or not the directory exists on the current file
   143  // system.
   144  func DirExists(name string) (bool, error) {
   145  	infos, err := os.Stat(name)
   146  	if err != nil {
   147  		if os.IsNotExist(err) {
   148  			return false, nil
   149  		}
   150  		return false, err
   151  	}
   152  	if !infos.IsDir() {
   153  		return false, fmt.Errorf("Path %s is not a directory", name)
   154  	}
   155  	return true, nil
   156  }
   157  
   158  // AbsPath returns an absolute path relative.
   159  func AbsPath(inPath string) string {
   160  	if strings.HasPrefix(inPath, "~") {
   161  		home, err := os.UserHomeDir()
   162  		if err != nil {
   163  			return ""
   164  		}
   165  		inPath = home + inPath[len("~"):]
   166  	} else if strings.HasPrefix(inPath, "$HOME") {
   167  		home, err := os.UserHomeDir()
   168  		if err != nil {
   169  			return ""
   170  		}
   171  		inPath = home + inPath[len("$HOME"):]
   172  	}
   173  
   174  	if strings.HasPrefix(inPath, "$") {
   175  		end := strings.Index(inPath, string(os.PathSeparator))
   176  		inPath = os.Getenv(inPath[1:end]) + inPath[end:]
   177  	}
   178  
   179  	p, err := filepath.Abs(inPath)
   180  	if err == nil {
   181  		return filepath.Clean(p)
   182  	}
   183  
   184  	return ""
   185  }
   186  
   187  // CleanUTF8 returns a string with only valid UTF-8 runes
   188  func CleanUTF8(s string) string {
   189  	if utf8.ValidString(s) {
   190  		return s
   191  	}
   192  	v := make([]rune, 0, len(s))
   193  	for _, r := range s {
   194  		if r != utf8.RuneError {
   195  			v = append(v, r)
   196  		}
   197  	}
   198  	return string(v)
   199  }
   200  
   201  // CloneURL clones the given url
   202  func CloneURL(u *url.URL) *url.URL {
   203  	clone := *u
   204  	if clone.User != nil {
   205  		tmp := *clone.User
   206  		clone.User = &tmp
   207  	}
   208  	return &clone
   209  }
   210  
   211  // DurationFuzzing returns a duration that is near the given duration, but
   212  // randomized to avoid patterns like several cache entries that expires at the
   213  // same time.
   214  func DurationFuzzing(d time.Duration, variation float64) time.Duration {
   215  	if variation > 1.0 || variation < 0.0 {
   216  		panic("DurationRandomized: variation should be between 0.0 and 1.0")
   217  	}
   218  	return time.Duration(float64(d) * (1.0 + variation*(2.0*rand.Float64()-1.0)))
   219  }