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 }