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  }