oss/utils.go (344 lines of code) (raw):
package oss
import (
"bytes"
"context"
"encoding"
"errors"
"fmt"
"io"
"net/http"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"time"
)
func init() {
for i := 0; i < len(noEscape); i++ {
noEscape[i] = (i >= 'A' && i <= 'Z') ||
(i >= 'a' && i <= 'z') ||
(i >= '0' && i <= '9') ||
i == '-' ||
i == '.' ||
i == '_' ||
i == '~'
}
defaultUserAgent = fmt.Sprintf("%s/%s (%s/%s/%s;%s)", SdkName, Version(), runtime.GOOS,
"-", runtime.GOARCH, runtime.Version())
}
var defaultUserAgent string
var noEscape [256]bool
func sleepWithContext(ctx context.Context, dur time.Duration) error {
t := time.NewTimer(dur)
defer t.Stop()
select {
case <-t.C:
break
case <-ctx.Done():
return ctx.Err()
}
return nil
}
// getNowSec returns Unix time, the number of seconds elapsed since January 1, 1970 UTC.
// gets the current time in Unix time, in seconds.
func getNowSec() int64 {
return time.Now().Unix()
}
// getNowGMT gets the current time in GMT format.
func getNowGMT() string {
return time.Now().UTC().Format(http.TimeFormat)
}
func escapePath(path string, encodeSep bool) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
if noEscape[c] || (c == '/' && !encodeSep) {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Pointer:
return v.IsNil()
}
return false
}
func setTimeReflectValue(dst reflect.Value, value time.Time) (err error) {
dst0 := dst
if dst.Kind() == reflect.Pointer {
if dst.IsNil() {
dst.Set(reflect.New(dst.Type().Elem()))
}
dst = dst.Elem()
}
if dst.CanAddr() {
pv := dst.Addr()
if pv.CanInterface() {
if val, ok := pv.Interface().(encoding.TextUnmarshaler); ok {
return val.UnmarshalText([]byte(value.Format(time.RFC3339)))
}
}
}
return errors.New("cannot unmarshal into " + dst0.Type().String())
}
func setReflectValue(dst reflect.Value, data string) (err error) {
dst0 := dst
src := []byte(data)
if dst.Kind() == reflect.Pointer {
if dst.IsNil() {
dst.Set(reflect.New(dst.Type().Elem()))
}
dst = dst.Elem()
}
switch dst.Kind() {
case reflect.Invalid:
default:
return errors.New("cannot unmarshal into " + dst0.Type().String())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if len(src) == 0 {
dst.SetInt(0)
return nil
}
itmp, err := strconv.ParseInt(strings.TrimSpace(string(src)), 10, dst.Type().Bits())
if err != nil {
return err
}
dst.SetInt(itmp)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if len(src) == 0 {
dst.SetUint(0)
return nil
}
utmp, err := strconv.ParseUint(strings.TrimSpace(string(src)), 10, dst.Type().Bits())
if err != nil {
return err
}
dst.SetUint(utmp)
case reflect.Bool:
if len(src) == 0 {
dst.SetBool(false)
return nil
}
value, err := strconv.ParseBool(strings.TrimSpace(string(src)))
if err != nil {
return err
}
dst.SetBool(value)
case reflect.String:
dst.SetString(string(src))
}
return nil
}
func setMapStringReflectValue(dst reflect.Value, key any, data any) (err error) {
dst0 := dst
if dst.Kind() == reflect.Pointer {
if dst.IsNil() {
dst.Set(reflect.New(dst.Type().Elem()))
}
dst = dst.Elem()
}
switch dst.Kind() {
case reflect.Invalid:
default:
return errors.New("cannot unmarshal into " + dst0.Type().String())
case reflect.Map:
if dst.IsNil() {
dst.Set(reflect.MakeMap(dst.Type()))
}
mapValue := reflect.ValueOf(data)
mapKey := reflect.ValueOf(key)
dst.SetMapIndex(mapKey, mapValue)
}
return nil
}
func isContextError(ctx context.Context, perr *error) bool {
if ctxErr := ctx.Err(); ctxErr != nil {
if *perr == nil {
*perr = ctxErr
}
return true
}
return false
}
func copySeekableBody(dst io.Writer, src io.ReadSeeker) (int64, error) {
curPos, err := src.Seek(0, io.SeekCurrent)
if err != nil {
return 0, err
}
n, err := io.Copy(dst, src)
if err != nil {
return n, err
}
_, err = src.Seek(curPos, io.SeekStart)
if err != nil {
return n, err
}
return n, nil
}
func ParseOffsetAndSizeFromHeaders(headers http.Header) (offset, size int64) {
return parseOffsetAndSizeFromHeaders(headers)
}
func parseOffsetAndSizeFromHeaders(headers http.Header) (offset, size int64) {
size = -1
var contentLength = headers.Get("Content-Length")
if len(contentLength) != 0 {
var err error
if size, err = strconv.ParseInt(contentLength, 10, 64); err != nil {
return 0, -1
}
}
var contentRange = headers.Get("Content-Range")
if len(contentRange) == 0 {
return 0, size
}
if !strings.HasPrefix(contentRange, "bytes ") {
return 0, -1
}
// start offset
dash := strings.IndexRune(contentRange, '-')
if dash < 0 {
return 0, -1
}
ret, err := strconv.ParseInt(contentRange[6:dash], 10, 64)
if err != nil {
return 0, -1
}
offset = ret
// total size
slash := strings.IndexRune(contentRange, '/')
if slash < 0 {
return 0, -1
}
tsize := contentRange[slash+1:]
if tsize != "*" {
ret, err = strconv.ParseInt(contentRange[slash+1:], 10, 64)
if err != nil {
return 0, -1
}
size = ret
}
return offset, size
}
func minInt64(a, b int64) int64 {
if a < b {
return a
} else {
return b
}
}
func maxInt64(a, b int64) int64 {
if a > b {
return a
} else {
return b
}
}
func minInt(a, b int) int {
if a < b {
return a
} else {
return b
}
}
func maxInt(a, b int) int {
if a > b {
return a
} else {
return b
}
}
// ParseRange parses a ContentRange from a ContentRange: header.
// It only accepts bytes 22-33/42 and bytes 22-33/* format.
func ParseContentRange(s string) (from int64, to int64, total int64, err error) {
if !strings.HasPrefix(s, "bytes ") {
return from, to, total, errors.New("invalid content range")
}
slash := strings.IndexRune(s, '/')
if slash < 0 {
return from, to, total, errors.New("invalid content range")
}
dash := strings.IndexRune(s, '-')
if dash < 0 {
return from, to, total, errors.New("invalid content range")
}
if slash < dash {
return from, to, total, errors.New("invalid content range")
}
// from
ret, err := strconv.ParseInt(s[6:dash], 10, 64)
if err != nil {
return from, to, total, errors.New("invalid content range")
}
from = ret
// to
ret, err = strconv.ParseInt(s[dash+1:slash], 10, 64)
if err != nil {
return from, to, total, errors.New("invalid content range")
}
to = ret
// total
last := s[slash+1:]
if last == "*" {
total = -1
} else {
ret, err = strconv.ParseInt(s[slash+1:], 10, 64)
if err != nil {
return from, to, total, errors.New("invalid content range")
}
total = ret
}
return from, to, total, nil
}
// ParseRange parses a HTTPRange from a Range: header.
// It only accepts single ranges.
func ParseRange(s string) (r *HTTPRange, err error) {
const preamble = "bytes="
if !strings.HasPrefix(s, preamble) {
return nil, errors.New("range: header invalid: doesn't start with " + preamble)
}
s = s[len(preamble):]
if strings.ContainsRune(s, ',') {
return nil, errors.New("range: header invalid: contains multiple ranges which isn't supported")
}
dash := strings.IndexRune(s, '-')
if dash < 0 {
return nil, errors.New("range: header invalid: contains no '-'")
}
start, end := strings.TrimSpace(s[:dash]), strings.TrimSpace(s[dash+1:])
o := HTTPRange{Offset: 0, Count: 0}
if start != "" {
o.Offset, err = strconv.ParseInt(start, 10, 64)
if err != nil || o.Offset < 0 {
return nil, errors.New("range: header invalid: bad start")
}
}
if end != "" {
e, err := strconv.ParseInt(end, 10, 64)
if err != nil || e < 0 {
return nil, errors.New("range: header invalid: bad end")
}
o.Count = e - o.Offset + 1
}
return &o, nil
}
// FileExists returns whether the given file exists or not
func FileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return (info != nil && !info.IsDir())
}
// DirExists returns whether the given directory exists or not
func DirExists(dir string) bool {
info, err := os.Stat(dir)
if os.IsNotExist(err) {
return false
}
return (info != nil && info.IsDir())
}
// EmptyFile changes the size of the named file to zero.
func EmptyFile(filename string) bool {
err := os.Truncate(filename, 0)
return err == nil
}