github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/utils.go (about) 1 /* 2 * Copyright (c) 2016, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package common 21 22 import ( 23 "bytes" 24 "compress/zlib" 25 "context" 26 "crypto/rand" 27 std_errors "errors" 28 "fmt" 29 "io" 30 "io/ioutil" 31 "math" 32 "net/url" 33 "os" 34 "time" 35 36 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 37 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/wildcard" 38 ) 39 40 const RFC3339Milli = "2006-01-02T15:04:05.000Z07:00" 41 42 // Contains is a helper function that returns true 43 // if the target string is in the list. 44 func Contains(list []string, target string) bool { 45 for _, listItem := range list { 46 if listItem == target { 47 return true 48 } 49 } 50 return false 51 } 52 53 // ContainsWildcard returns true if target matches 54 // any of the patterns. Patterns may contain the 55 // '*' wildcard. 56 func ContainsWildcard(patterns []string, target string) bool { 57 for _, pattern := range patterns { 58 if wildcard.Match(pattern, target) { 59 return true 60 } 61 } 62 return false 63 } 64 65 // ContainsAny returns true if any string in targets 66 // is present in the list. 67 func ContainsAny(list, targets []string) bool { 68 for _, target := range targets { 69 if Contains(list, target) { 70 return true 71 } 72 } 73 return false 74 } 75 76 // ContainsInt returns true if the target int is 77 // in the list. 78 func ContainsInt(list []int, target int) bool { 79 for _, listItem := range list { 80 if listItem == target { 81 return true 82 } 83 } 84 return false 85 } 86 87 // GetStringSlice converts an interface{} which is 88 // of type []interace{}, and with the type of each 89 // element a string, to []string. 90 func GetStringSlice(value interface{}) ([]string, bool) { 91 slice, ok := value.([]interface{}) 92 if !ok { 93 return nil, false 94 } 95 strSlice := make([]string, len(slice)) 96 for index, element := range slice { 97 str, ok := element.(string) 98 if !ok { 99 return nil, false 100 } 101 strSlice[index] = str 102 } 103 return strSlice, true 104 } 105 106 // MakeSecureRandomBytes is a helper function that wraps 107 // crypto/rand.Read. 108 func MakeSecureRandomBytes(length int) ([]byte, error) { 109 randomBytes := make([]byte, length) 110 _, err := rand.Read(randomBytes) 111 if err != nil { 112 return nil, errors.Trace(err) 113 } 114 return randomBytes, nil 115 } 116 117 // GetCurrentTimestamp returns the current time in UTC as 118 // an RFC 3339 formatted string. 119 func GetCurrentTimestamp() string { 120 return time.Now().UTC().Format(time.RFC3339) 121 } 122 123 // TruncateTimestampToHour truncates an RFC 3339 formatted string 124 // to hour granularity. If the input is not a valid format, the 125 // result is "". 126 func TruncateTimestampToHour(timestamp string) string { 127 t, err := time.Parse(time.RFC3339, timestamp) 128 if err != nil { 129 return "" 130 } 131 return t.Truncate(1 * time.Hour).Format(time.RFC3339) 132 } 133 134 // Compress returns zlib compressed data 135 func Compress(data []byte) []byte { 136 var compressedData bytes.Buffer 137 writer := zlib.NewWriter(&compressedData) 138 writer.Write(data) 139 writer.Close() 140 return compressedData.Bytes() 141 } 142 143 // Decompress returns zlib decompressed data 144 func Decompress(data []byte) ([]byte, error) { 145 reader, err := zlib.NewReader(bytes.NewReader(data)) 146 if err != nil { 147 return nil, errors.Trace(err) 148 } 149 uncompressedData, err := ioutil.ReadAll(reader) 150 reader.Close() 151 if err != nil { 152 return nil, errors.Trace(err) 153 } 154 return uncompressedData, nil 155 } 156 157 // FormatByteCount returns a string representation of the specified 158 // byte count in conventional, human-readable format. 159 func FormatByteCount(bytes uint64) string { 160 // Based on: https://bitbucket.org/psiphon/psiphon-circumvention-system/src/b2884b0d0a491e55420ed1888aea20d00fefdb45/Android/app/src/main/java/com/psiphon3/psiphonlibrary/Utils.java?at=default#Utils.java-646 161 base := uint64(1024) 162 if bytes < base { 163 return fmt.Sprintf("%dB", bytes) 164 } 165 exp := int(math.Log(float64(bytes)) / math.Log(float64(base))) 166 return fmt.Sprintf( 167 "%.1f%c", float64(bytes)/math.Pow(float64(base), float64(exp)), "KMGTPEZ"[exp-1]) 168 } 169 170 // CopyBuffer calls io.CopyBuffer, masking out any src.WriteTo or dst.ReadFrom 171 // to force use of the specified buf. 172 func CopyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { 173 return io.CopyBuffer(struct{ io.Writer }{dst}, struct{ io.Reader }{src}, buf) 174 } 175 176 func CopyNBuffer(dst io.Writer, src io.Reader, n int64, buf []byte) (written int64, err error) { 177 // Based on io.CopyN: 178 // https://github.com/golang/go/blob/release-branch.go1.11/src/io/io.go#L339 179 written, err = CopyBuffer(dst, io.LimitReader(src, n), buf) 180 if written == n { 181 return n, nil 182 } 183 if written < n && err == nil { 184 err = io.EOF 185 } 186 return 187 } 188 189 // FileExists returns true if a file, or directory, exists at the given path. 190 func FileExists(filePath string) bool { 191 if _, err := os.Stat(filePath); err != nil && os.IsNotExist(err) { 192 return false 193 } 194 return true 195 } 196 197 // SafeParseURL wraps url.Parse, stripping the input URL from any error 198 // message. This allows logging url.Parse errors without unintentially logging 199 // PII that may appear in the input URL. 200 func SafeParseURL(rawurl string) (*url.URL, error) { 201 parsedURL, err := url.Parse(rawurl) 202 if err != nil { 203 // Unwrap yields just the url.Error error field without the url.Error URL 204 // and operation fields. 205 err = std_errors.Unwrap(err) 206 if err == nil { 207 err = std_errors.New("SafeParseURL: Unwrap failed") 208 } else { 209 err = fmt.Errorf("url.Parse: %v", err) 210 } 211 } 212 return parsedURL, err 213 } 214 215 // SafeParseRequestURI wraps url.ParseRequestURI, stripping the input URL from 216 // any error message. This allows logging url.ParseRequestURI errors without 217 // unintentially logging PII that may appear in the input URL. 218 func SafeParseRequestURI(rawurl string) (*url.URL, error) { 219 parsedURL, err := url.ParseRequestURI(rawurl) 220 if err != nil { 221 err = std_errors.Unwrap(err) 222 if err == nil { 223 err = std_errors.New("SafeParseRequestURI: Unwrap failed") 224 } else { 225 err = fmt.Errorf("url.ParseRequestURI: %v", err) 226 } 227 } 228 return parsedURL, err 229 } 230 231 // SleepWithContext returns after the specified duration or once the input ctx 232 // is done, whichever is first. 233 func SleepWithContext(ctx context.Context, duration time.Duration) { 234 timer := time.NewTimer(duration) 235 defer timer.Stop() 236 select { 237 case <-timer.C: 238 case <-ctx.Done(): 239 } 240 }