github.com/chnsz/golangsdk@v0.0.0-20240506093406-85a3fbfa605b/openstack/obs/util.go (about)

     1  // Copyright 2019 Huawei Technologies Co.,Ltd.
     2  // Licensed under the Apache License, Version 2.0 (the "License"); you may not use
     3  // this file except in compliance with the License.  You may obtain a copy of the
     4  // License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software distributed
     9  // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    10  // CONDITIONS OF ANY KIND, either express or implied.  See the License for the
    11  // specific language governing permissions and limitations under the License.
    12  
    13  package obs
    14  
    15  import (
    16  	"bytes"
    17  	"crypto/hmac"
    18  	"crypto/md5"
    19  	"crypto/sha1"
    20  	"crypto/sha256"
    21  	"encoding/base64"
    22  	"encoding/hex"
    23  	"encoding/json"
    24  	"encoding/xml"
    25  	"fmt"
    26  	"io"
    27  	"net/http"
    28  	"net/url"
    29  	"os"
    30  	"regexp"
    31  	"strconv"
    32  	"strings"
    33  	"time"
    34  )
    35  
    36  var regex = regexp.MustCompile("^[\u4e00-\u9fa5]$")
    37  var ipRegex = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$")
    38  var v4AuthRegex = regexp.MustCompile("Credential=(.+?),SignedHeaders=(.+?),Signature=.+")
    39  var regionRegex = regexp.MustCompile(".+/\\d+/(.+?)/.+")
    40  
    41  // StringContains replaces subStr in src with subTranscoding and returns the new string
    42  func StringContains(src string, subStr string, subTranscoding string) string {
    43  	return strings.Replace(src, subStr, subTranscoding, -1)
    44  }
    45  
    46  // XmlTranscoding replaces special characters with their escaped form
    47  func XmlTranscoding(src string) string {
    48  	srcTmp := StringContains(src, "&", "&")
    49  	srcTmp = StringContains(srcTmp, "<", "&lt;")
    50  	srcTmp = StringContains(srcTmp, ">", "&gt;")
    51  	srcTmp = StringContains(srcTmp, "'", "&apos;")
    52  	srcTmp = StringContains(srcTmp, "\"", "&quot;")
    53  	return srcTmp
    54  }
    55  
    56  func HandleHttpResponse(action string, headers map[string][]string, output IBaseModel, resp *http.Response, xmlResult bool, isObs bool) (err error) {
    57  	if IsHandleCallbackResponse(action, headers, isObs) {
    58  		if err = ParseCallbackResponseToBaseModel(resp, output, isObs); err != nil {
    59  			doLog(LEVEL_WARN, "Parse callback response to BaseModel with error: %v", err)
    60  		}
    61  	} else {
    62  		if err = ParseResponseToBaseModel(resp, output, xmlResult, isObs); err != nil {
    63  			doLog(LEVEL_WARN, "Parse response to BaseModel with error: %v", err)
    64  		}
    65  	}
    66  	return
    67  }
    68  
    69  func IsHandleCallbackResponse(action string, headers map[string][]string, isObs bool) bool {
    70  	var headerPrefix = HEADER_PREFIX
    71  	if isObs == true {
    72  		headerPrefix = HEADER_PREFIX_OBS
    73  	}
    74  	supportCallbackActions := []string{PUT_OBJECT, PUT_FILE, "CompleteMultipartUpload"}
    75  	return len(headers[headerPrefix+CALLBACK]) != 0 && IsContain(supportCallbackActions, action)
    76  }
    77  
    78  func IsContain(items []string, item string) bool {
    79  	for _, eachItem := range items {
    80  		if eachItem == item {
    81  			return true
    82  		}
    83  	}
    84  	return false
    85  }
    86  
    87  // StringToInt converts string value to int value with default value
    88  func StringToInt(value string, def int) int {
    89  	ret, err := strconv.Atoi(value)
    90  	if err != nil {
    91  		ret = def
    92  	}
    93  	return ret
    94  }
    95  
    96  // StringToInt64 converts string value to int64 value with default value
    97  func StringToInt64(value string, def int64) int64 {
    98  	ret, err := strconv.ParseInt(value, 10, 64)
    99  	if err != nil {
   100  		ret = def
   101  	}
   102  	return ret
   103  }
   104  
   105  // IntToString converts int value to string value
   106  func IntToString(value int) string {
   107  	return strconv.Itoa(value)
   108  }
   109  
   110  // Int64ToString converts int64 value to string value
   111  func Int64ToString(value int64) string {
   112  	return strconv.FormatInt(value, 10)
   113  }
   114  
   115  // GetCurrentTimestamp gets unix time in milliseconds
   116  func GetCurrentTimestamp() int64 {
   117  	return time.Now().UnixNano() / 1000000
   118  }
   119  
   120  // FormatUtcNow gets a textual representation of the UTC format time value
   121  func FormatUtcNow(format string) string {
   122  	return time.Now().UTC().Format(format)
   123  }
   124  
   125  // FormatNow gets a textual representation of the format time value
   126  func FormatNow(format string) string {
   127  	return time.Now().Format(format)
   128  }
   129  
   130  // FormatUtcToRfc1123 gets a textual representation of the RFC1123 format time value
   131  func FormatUtcToRfc1123(t time.Time) string {
   132  	ret := t.UTC().Format(time.RFC1123)
   133  	return ret[:strings.LastIndex(ret, "UTC")] + "GMT"
   134  }
   135  
   136  // Md5 gets the md5 value of input
   137  func Md5(value []byte) []byte {
   138  	m := md5.New()
   139  	_, err := m.Write(value)
   140  	if err != nil {
   141  		doLog(LEVEL_WARN, "MD5 failed to write")
   142  	}
   143  	return m.Sum(nil)
   144  }
   145  
   146  // HmacSha1 gets hmac sha1 value of input
   147  func HmacSha1(key, value []byte) []byte {
   148  	mac := hmac.New(sha1.New, key)
   149  	_, err := mac.Write(value)
   150  	if err != nil {
   151  		doLog(LEVEL_WARN, "HmacSha1 failed to write")
   152  	}
   153  	return mac.Sum(nil)
   154  }
   155  
   156  // HmacSha256 get hmac sha256 value if input
   157  func HmacSha256(key, value []byte) []byte {
   158  	mac := hmac.New(sha256.New, key)
   159  	_, err := mac.Write(value)
   160  	if err != nil {
   161  		doLog(LEVEL_WARN, "HmacSha256 failed to write")
   162  	}
   163  	return mac.Sum(nil)
   164  }
   165  
   166  // Base64Encode wrapper of base64.StdEncoding.EncodeToString
   167  func Base64Encode(value []byte) string {
   168  	return base64.StdEncoding.EncodeToString(value)
   169  }
   170  
   171  // Base64Decode wrapper of base64.StdEncoding.DecodeString
   172  func Base64Decode(value string) ([]byte, error) {
   173  	return base64.StdEncoding.DecodeString(value)
   174  }
   175  
   176  // HexMd5 returns the md5 value of input in hexadecimal format
   177  func HexMd5(value []byte) string {
   178  	return Hex(Md5(value))
   179  }
   180  
   181  // Base64Md5 returns the md5 value of input with Base64Encode
   182  func Base64Md5(value []byte) string {
   183  	return Base64Encode(Md5(value))
   184  }
   185  
   186  // Sha256Hash returns sha256 checksum
   187  func Sha256Hash(value []byte) []byte {
   188  	hash := sha256.New()
   189  	_, err := hash.Write(value)
   190  	if err != nil {
   191  		doLog(LEVEL_WARN, "Sha256Hash failed to write")
   192  	}
   193  	return hash.Sum(nil)
   194  }
   195  
   196  // ParseXml wrapper of xml.Unmarshal
   197  func ParseXml(value []byte, result interface{}) error {
   198  	if len(value) == 0 {
   199  		return nil
   200  	}
   201  	return xml.Unmarshal(value, result)
   202  }
   203  
   204  // parseJSON wrapper of json.Unmarshal
   205  func parseJSON(value []byte, result interface{}) error {
   206  	if len(value) == 0 {
   207  		return nil
   208  	}
   209  	return json.Unmarshal(value, result)
   210  }
   211  
   212  // TransToXml wrapper of xml.Marshal
   213  func TransToXml(value interface{}) ([]byte, error) {
   214  	if value == nil {
   215  		return []byte{}, nil
   216  	}
   217  	return xml.Marshal(value)
   218  }
   219  
   220  // Hex wrapper of hex.EncodeToString
   221  func Hex(value []byte) string {
   222  	return hex.EncodeToString(value)
   223  }
   224  
   225  // HexSha256 returns the Sha256Hash value of input in hexadecimal format
   226  func HexSha256(value []byte) string {
   227  	return Hex(Sha256Hash(value))
   228  }
   229  
   230  // UrlDecode wrapper of url.QueryUnescape
   231  func UrlDecode(value string) (string, error) {
   232  	ret, err := url.QueryUnescape(value)
   233  	if err == nil {
   234  		return ret, nil
   235  	}
   236  	return "", err
   237  }
   238  
   239  // UrlDecodeWithoutError wrapper of UrlDecode
   240  func UrlDecodeWithoutError(value string) string {
   241  	ret, err := UrlDecode(value)
   242  	if err == nil {
   243  		return ret
   244  	}
   245  	if isErrorLogEnabled() {
   246  		doLog(LEVEL_ERROR, "Url decode error")
   247  	}
   248  	return ""
   249  }
   250  
   251  // IsIP checks whether the value matches ip address
   252  func IsIP(value string) bool {
   253  	return ipRegex.MatchString(value)
   254  }
   255  
   256  // UrlEncode encodes the input value
   257  func UrlEncode(value string, chineseOnly bool) string {
   258  	if chineseOnly {
   259  		values := make([]string, 0, len(value))
   260  		for _, val := range value {
   261  			_value := string(val)
   262  			if regex.MatchString(_value) {
   263  				_value = url.QueryEscape(_value)
   264  			}
   265  			values = append(values, _value)
   266  		}
   267  		return strings.Join(values, "")
   268  	}
   269  	return url.QueryEscape(value)
   270  }
   271  
   272  func copyHeaders(m map[string][]string) (ret map[string][]string) {
   273  	if m != nil {
   274  		ret = make(map[string][]string, len(m))
   275  		for key, values := range m {
   276  			_values := make([]string, 0, len(values))
   277  			for _, value := range values {
   278  				_values = append(_values, value)
   279  			}
   280  			ret[strings.ToLower(key)] = _values
   281  		}
   282  	} else {
   283  		ret = make(map[string][]string)
   284  	}
   285  
   286  	return
   287  }
   288  
   289  func parseHeaders(headers map[string][]string) (signature string, region string, signedHeaders string) {
   290  	signature = "v2"
   291  	if receviedAuthorization, ok := headers[strings.ToLower(HEADER_AUTH_CAMEL)]; ok && len(receviedAuthorization) > 0 {
   292  		if strings.HasPrefix(receviedAuthorization[0], V4_HASH_PREFIX) {
   293  			signature = "v4"
   294  			matches := v4AuthRegex.FindStringSubmatch(receviedAuthorization[0])
   295  			if len(matches) >= 3 {
   296  				region = matches[1]
   297  				regions := regionRegex.FindStringSubmatch(region)
   298  				if len(regions) >= 2 {
   299  					region = regions[1]
   300  				}
   301  				signedHeaders = matches[2]
   302  			}
   303  
   304  		} else if strings.HasPrefix(receviedAuthorization[0], V2_HASH_PREFIX) {
   305  			signature = "v2"
   306  		}
   307  	}
   308  	return
   309  }
   310  
   311  func getTemporaryKeys() []string {
   312  	return []string{
   313  		"Signature",
   314  		"signature",
   315  		"X-Amz-Signature",
   316  		"x-amz-signature",
   317  	}
   318  }
   319  
   320  func getIsObs(isTemporary bool, querys []string, headers map[string][]string) bool {
   321  	isObs := true
   322  	if isTemporary {
   323  		for _, value := range querys {
   324  			keyPrefix := strings.ToLower(value)
   325  			if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
   326  				isObs = false
   327  			} else if strings.HasPrefix(value, HEADER_ACCESSS_KEY_AMZ) {
   328  				isObs = false
   329  			}
   330  		}
   331  	} else {
   332  		for key := range headers {
   333  			keyPrefix := strings.ToLower(key)
   334  			if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
   335  				isObs = false
   336  				break
   337  			}
   338  		}
   339  	}
   340  	return isObs
   341  }
   342  
   343  func isPathStyle(headers map[string][]string, bucketName string) bool {
   344  	if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") {
   345  		return true
   346  	}
   347  	return false
   348  }
   349  
   350  // GetV2Authorization v2 Authorization
   351  func GetV2Authorization(ak, sk, method, bucketName, objectKey, queryURL string, headers map[string][]string) (ret map[string]string) {
   352  
   353  	if strings.HasPrefix(queryURL, "?") {
   354  		queryURL = queryURL[1:]
   355  	}
   356  
   357  	method = strings.ToUpper(method)
   358  
   359  	querys := strings.Split(queryURL, "&")
   360  	querysResult := make([]string, 0)
   361  	for _, value := range querys {
   362  		if value != "=" && len(value) != 0 {
   363  			querysResult = append(querysResult, value)
   364  		}
   365  	}
   366  	params := make(map[string]string)
   367  
   368  	for _, value := range querysResult {
   369  		kv := strings.Split(value, "=")
   370  		length := len(kv)
   371  		if length == 1 {
   372  			key := UrlDecodeWithoutError(kv[0])
   373  			params[key] = ""
   374  		} else if length >= 2 {
   375  			key := UrlDecodeWithoutError(kv[0])
   376  			vals := make([]string, 0, length-1)
   377  			for i := 1; i < length; i++ {
   378  				val := UrlDecodeWithoutError(kv[i])
   379  				vals = append(vals, val)
   380  			}
   381  			params[key] = strings.Join(vals, "=")
   382  		}
   383  	}
   384  	headers = copyHeaders(headers)
   385  	pathStyle := isPathStyle(headers, bucketName)
   386  	conf := &config{securityProviders: []securityProvider{NewBasicSecurityProvider(ak, sk, "")},
   387  		urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
   388  		pathStyle: pathStyle}
   389  	conf.signature = SignatureObs
   390  	_, canonicalizedURL := conf.formatUrls(bucketName, objectKey, params, false)
   391  	ret = v2Auth(ak, sk, method, canonicalizedURL, headers, true)
   392  	v2HashPrefix := OBS_HASH_PREFIX
   393  	ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
   394  	return
   395  }
   396  
   397  func getQuerysResult(querys []string) []string {
   398  	querysResult := make([]string, 0)
   399  	for _, value := range querys {
   400  		if value != "=" && len(value) != 0 {
   401  			querysResult = append(querysResult, value)
   402  		}
   403  	}
   404  	return querysResult
   405  }
   406  
   407  func getParams(querysResult []string) map[string]string {
   408  	params := make(map[string]string)
   409  	for _, value := range querysResult {
   410  		kv := strings.Split(value, "=")
   411  		length := len(kv)
   412  		if length == 1 {
   413  			key := UrlDecodeWithoutError(kv[0])
   414  			params[key] = ""
   415  		} else if length >= 2 {
   416  			key := UrlDecodeWithoutError(kv[0])
   417  			vals := make([]string, 0, length-1)
   418  			for i := 1; i < length; i++ {
   419  				val := UrlDecodeWithoutError(kv[i])
   420  				vals = append(vals, val)
   421  			}
   422  			params[key] = strings.Join(vals, "=")
   423  		}
   424  	}
   425  	return params
   426  }
   427  
   428  func getTemporaryAndSignature(params map[string]string) (bool, string) {
   429  	isTemporary := false
   430  	signature := "v2"
   431  	temporaryKeys := getTemporaryKeys()
   432  	for _, key := range temporaryKeys {
   433  		if _, ok := params[key]; ok {
   434  			isTemporary = true
   435  			if strings.ToLower(key) == "signature" {
   436  				signature = "v2"
   437  			} else if strings.ToLower(key) == "x-amz-signature" {
   438  				signature = "v4"
   439  			}
   440  			break
   441  		}
   442  	}
   443  	return isTemporary, signature
   444  }
   445  
   446  // GetAuthorization Authorization
   447  func GetAuthorization(ak, sk, method, bucketName, objectKey, queryURL string, headers map[string][]string) (ret map[string]string) {
   448  
   449  	if strings.HasPrefix(queryURL, "?") {
   450  		queryURL = queryURL[1:]
   451  	}
   452  
   453  	method = strings.ToUpper(method)
   454  
   455  	querys := strings.Split(queryURL, "&")
   456  	querysResult := getQuerysResult(querys)
   457  	params := getParams(querysResult)
   458  
   459  	isTemporary, signature := getTemporaryAndSignature(params)
   460  
   461  	isObs := getIsObs(isTemporary, querysResult, headers)
   462  	headers = copyHeaders(headers)
   463  	pathStyle := false
   464  	if receviedHost, ok := headers[HEADER_HOST]; ok && len(receviedHost) > 0 && !strings.HasPrefix(receviedHost[0], bucketName+".") {
   465  		pathStyle = true
   466  	}
   467  	conf := &config{securityProviders: []securityProvider{NewBasicSecurityProvider(ak, sk, "")},
   468  		urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
   469  		pathStyle: pathStyle}
   470  
   471  	if isTemporary {
   472  		return getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature, conf, params, headers, isObs)
   473  	}
   474  	signature, region, signedHeaders := parseHeaders(headers)
   475  	if signature == "v4" {
   476  		conf.signature = SignatureV4
   477  		requestURL, canonicalizedURL := conf.formatUrls(bucketName, objectKey, params, false)
   478  		parsedRequestURL, _err := url.Parse(requestURL)
   479  		if _err != nil {
   480  			doLog(LEVEL_WARN, "Failed to parse requestURL")
   481  			return nil
   482  		}
   483  		headerKeys := strings.Split(signedHeaders, ";")
   484  		_headers := make(map[string][]string, len(headerKeys))
   485  		for _, headerKey := range headerKeys {
   486  			_headers[headerKey] = headers[headerKey]
   487  		}
   488  		ret = v4Auth(ak, sk, region, method, canonicalizedURL, parsedRequestURL.RawQuery, _headers)
   489  		ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
   490  	} else if signature == "v2" {
   491  		if isObs {
   492  			conf.signature = SignatureObs
   493  		} else {
   494  			conf.signature = SignatureV2
   495  		}
   496  		_, canonicalizedURL := conf.formatUrls(bucketName, objectKey, params, false)
   497  		ret = v2Auth(ak, sk, method, canonicalizedURL, headers, isObs)
   498  		v2HashPrefix := V2_HASH_PREFIX
   499  		if isObs {
   500  			v2HashPrefix = OBS_HASH_PREFIX
   501  		}
   502  		ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
   503  	}
   504  	return
   505  
   506  }
   507  
   508  func getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature string, conf *config, params map[string]string,
   509  	headers map[string][]string, isObs bool) (ret map[string]string) {
   510  
   511  	if signature == "v4" {
   512  		conf.signature = SignatureV4
   513  
   514  		longDate, ok := params[PARAM_DATE_AMZ_CAMEL]
   515  		if !ok {
   516  			longDate = params[HEADER_DATE_AMZ]
   517  		}
   518  		shortDate := longDate[:8]
   519  
   520  		credential, ok := params[PARAM_CREDENTIAL_AMZ_CAMEL]
   521  		if !ok {
   522  			credential = params[strings.ToLower(PARAM_CREDENTIAL_AMZ_CAMEL)]
   523  		}
   524  
   525  		_credential := UrlDecodeWithoutError(credential)
   526  
   527  		regions := regionRegex.FindStringSubmatch(_credential)
   528  		var region string
   529  		if len(regions) >= 2 {
   530  			region = regions[1]
   531  		}
   532  
   533  		_, scope := getCredential(ak, region, shortDate)
   534  
   535  		expires, ok := params[PARAM_EXPIRES_AMZ_CAMEL]
   536  		if !ok {
   537  			expires = params[strings.ToLower(PARAM_EXPIRES_AMZ_CAMEL)]
   538  		}
   539  
   540  		signedHeaders, ok := params[PARAM_SIGNEDHEADERS_AMZ_CAMEL]
   541  		if !ok {
   542  			signedHeaders = params[strings.ToLower(PARAM_SIGNEDHEADERS_AMZ_CAMEL)]
   543  		}
   544  
   545  		algorithm, ok := params[PARAM_ALGORITHM_AMZ_CAMEL]
   546  		if !ok {
   547  			algorithm = params[strings.ToLower(PARAM_ALGORITHM_AMZ_CAMEL)]
   548  		}
   549  
   550  		if _, ok := params[PARAM_SIGNATURE_AMZ_CAMEL]; ok {
   551  			delete(params, PARAM_SIGNATURE_AMZ_CAMEL)
   552  		} else if _, ok := params[strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL)]; ok {
   553  			delete(params, strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL))
   554  		}
   555  
   556  		ret = make(map[string]string, 6)
   557  		ret[PARAM_ALGORITHM_AMZ_CAMEL] = algorithm
   558  		ret[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
   559  		ret[PARAM_DATE_AMZ_CAMEL] = longDate
   560  		ret[PARAM_EXPIRES_AMZ_CAMEL] = expires
   561  		ret[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = signedHeaders
   562  
   563  		requestURL, canonicalizedURL := conf.formatUrls(bucketName, objectKey, params, false)
   564  		parsedRequestURL, _err := url.Parse(requestURL)
   565  		if _err != nil {
   566  			doLog(LEVEL_WARN, "Failed to parse requestUrl")
   567  			return nil
   568  		}
   569  		stringToSign := getV4StringToSign(method, canonicalizedURL, parsedRequestURL.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, strings.Split(signedHeaders, ";"), headers)
   570  		ret[PARAM_SIGNATURE_AMZ_CAMEL] = UrlEncode(getSignature(stringToSign, sk, region, shortDate), false)
   571  	} else if signature == "v2" {
   572  		if isObs {
   573  			conf.signature = SignatureObs
   574  		} else {
   575  			conf.signature = SignatureV2
   576  		}
   577  		_, canonicalizedURL := conf.formatUrls(bucketName, objectKey, params, false)
   578  		expires, ok := params["Expires"]
   579  		if !ok {
   580  			expires = params["expires"]
   581  		}
   582  		headers[HEADER_DATE_CAMEL] = []string{expires}
   583  		stringToSign := getV2StringToSign(method, canonicalizedURL, headers, isObs)
   584  		ret = make(map[string]string, 3)
   585  		ret["Signature"] = UrlEncode(Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign))), false)
   586  		ret["AWSAccessKeyId"] = UrlEncode(ak, false)
   587  		ret["Expires"] = UrlEncode(expires, false)
   588  	}
   589  
   590  	return
   591  }
   592  
   593  func GetContentType(key string) (string, bool) {
   594  	if ct, ok := mimeTypes[strings.ToLower(key[strings.LastIndex(key, ".")+1:])]; ok {
   595  		return ct, ok
   596  	}
   597  	return "", false
   598  }
   599  
   600  func GetReaderLen(reader io.Reader) (int64, error) {
   601  	var contentLength int64
   602  	var err error
   603  	switch v := reader.(type) {
   604  	case *bytes.Buffer:
   605  		contentLength = int64(v.Len())
   606  	case *bytes.Reader:
   607  		contentLength = int64(v.Len())
   608  	case *strings.Reader:
   609  		contentLength = int64(v.Len())
   610  	case *os.File:
   611  		fInfo, fError := v.Stat()
   612  		if fError != nil {
   613  			err = fmt.Errorf("can't get reader content length,%s", fError.Error())
   614  		} else {
   615  			contentLength = fInfo.Size()
   616  		}
   617  	case *io.LimitedReader:
   618  		contentLength = int64(v.N)
   619  	case *fileReaderWrapper:
   620  		contentLength = int64(v.totalCount)
   621  	case *readerWrapper:
   622  		contentLength = int64(v.totalCount)
   623  	default:
   624  		err = fmt.Errorf("can't get reader content length,unkown reader type")
   625  	}
   626  	return contentLength, err
   627  }