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 }