github.com/opentelekomcloud/gophertelekomcloud@v0.9.3/openstack/obs/util.go (about)

     1  package obs
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"crypto/md5"
     6  	"crypto/sha1"
     7  	"crypto/sha256"
     8  	"encoding/base64"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"encoding/xml"
    12  	"fmt"
    13  	"net/url"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  )
    19  
    20  var (
    21  	regex       = regexp.MustCompile("^[\u4e00-\u9fa5]$")
    22  	ipRegex     = regexp.MustCompile(`^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$`)
    23  	v4AuthRegex = regexp.MustCompile(`Credential=(.+?),SignedHeaders=(.+?),Signature=.+`)
    24  	regionRegex = regexp.MustCompile(`.+/\d+/(.+?)/.+`)
    25  )
    26  
    27  // StringContains replaces subStr in src with subTranscoding and returns the new string
    28  func StringContains(src string, subStr string, subTranscoding string) string {
    29  	return strings.Replace(src, subStr, subTranscoding, -1)
    30  }
    31  
    32  // XmlTranscoding replaces special characters with their escaped form
    33  func XmlTranscoding(src string) string {
    34  	srcTmp := StringContains(src, "&", "&")
    35  	srcTmp = StringContains(srcTmp, "<", "&lt;")
    36  	srcTmp = StringContains(srcTmp, ">", "&gt;")
    37  	srcTmp = StringContains(srcTmp, "'", "&apos;")
    38  	srcTmp = StringContains(srcTmp, "\"", "&quot;")
    39  	return srcTmp
    40  }
    41  
    42  // StringToInt converts string value to int value with default value
    43  func StringToInt(value string, def int) int {
    44  	ret, err := strconv.Atoi(value)
    45  	if err != nil {
    46  		ret = def
    47  	}
    48  	return ret
    49  }
    50  
    51  // StringToInt64 converts string value to int64 value with default value
    52  func StringToInt64(value string, def int64) int64 {
    53  	ret, err := strconv.ParseInt(value, 10, 64)
    54  	if err != nil {
    55  		ret = def
    56  	}
    57  	return ret
    58  }
    59  
    60  // IntToString converts int value to string value
    61  func IntToString(value int) string {
    62  	return strconv.Itoa(value)
    63  }
    64  
    65  // Int64ToString converts int64 value to string value
    66  func Int64ToString(value int64) string {
    67  	return strconv.FormatInt(value, 10)
    68  }
    69  
    70  // GetCurrentTimestamp gets unix time in milliseconds
    71  func GetCurrentTimestamp() int64 {
    72  	return time.Now().UnixNano() / 1000000
    73  }
    74  
    75  // FormatUtcNow gets a textual representation of the UTC format time value
    76  func FormatUtcNow(format string) string {
    77  	return time.Now().UTC().Format(format)
    78  }
    79  
    80  // FormatUtcToRfc1123 gets a textual representation of the RFC1123 format time value
    81  func FormatUtcToRfc1123(t time.Time) string {
    82  	ret := t.UTC().Format(time.RFC1123)
    83  	return ret[:strings.LastIndex(ret, "UTC")] + "GMT"
    84  }
    85  
    86  // Md5 gets the md5 value of input
    87  func Md5(value []byte) []byte {
    88  	m := md5.New()
    89  	_, err := m.Write(value)
    90  	if err != nil {
    91  		doLog(LEVEL_WARN, "MD5 failed to write with reason: %v", err)
    92  	}
    93  	return m.Sum(nil)
    94  }
    95  
    96  // HmacSha1 gets hmac sha1 value of input
    97  func HmacSha1(key, value []byte) []byte {
    98  	mac := hmac.New(sha1.New, key)
    99  	_, err := mac.Write(value)
   100  	if err != nil {
   101  		doLog(LEVEL_WARN, "HmacSha1 failed to write with reason: %v", err)
   102  	}
   103  	return mac.Sum(nil)
   104  }
   105  
   106  // HmacSha256 get hmac sha256 value if input
   107  func HmacSha256(key, value []byte) []byte {
   108  	mac := hmac.New(sha256.New, key)
   109  	_, err := mac.Write(value)
   110  	if err != nil {
   111  		doLog(LEVEL_WARN, "HmacSha256 failed to write with reason: %v", err)
   112  	}
   113  	return mac.Sum(nil)
   114  }
   115  
   116  // Base64Encode wrapper of base64.StdEncoding.EncodeToString
   117  func Base64Encode(value []byte) string {
   118  	return base64.StdEncoding.EncodeToString(value)
   119  }
   120  
   121  // Base64Decode wrapper of base64.StdEncoding.DecodeString
   122  func Base64Decode(value string) ([]byte, error) {
   123  	return base64.StdEncoding.DecodeString(value)
   124  }
   125  
   126  // HexMd5 returns the md5 value of input in hexadecimal format
   127  func HexMd5(value []byte) string {
   128  	return Hex(Md5(value))
   129  }
   130  
   131  // Base64Md5 returns the md5 value of input with Base64Encode
   132  func Base64Md5(value []byte) string {
   133  	return Base64Encode(Md5(value))
   134  }
   135  
   136  // Sha256Hash returns sha256 checksum
   137  func Sha256Hash(value []byte) []byte {
   138  	hash := sha256.New()
   139  	_, err := hash.Write(value)
   140  	if err != nil {
   141  		doLog(LEVEL_WARN, "Sha256Hash failed to write with reason: %v", err)
   142  	}
   143  	return hash.Sum(nil)
   144  }
   145  
   146  // ParseXml wrapper of xml.Unmarshal
   147  func ParseXml(value []byte, result interface{}) error {
   148  	if len(value) == 0 {
   149  		return nil
   150  	}
   151  	return xml.Unmarshal(value, result)
   152  }
   153  
   154  // parseJSON wrapper of json.Unmarshal
   155  func parseJSON(value []byte, result interface{}) error {
   156  	if len(value) == 0 {
   157  		return nil
   158  	}
   159  	return json.Unmarshal(value, result)
   160  }
   161  
   162  // TransToXml wrapper of xml.Marshal
   163  func TransToXml(value interface{}) ([]byte, error) {
   164  	if value == nil {
   165  		return []byte{}, nil
   166  	}
   167  	return xml.Marshal(value)
   168  }
   169  
   170  // Hex wrapper of hex.EncodeToString
   171  func Hex(value []byte) string {
   172  	return hex.EncodeToString(value)
   173  }
   174  
   175  // HexSha256 returns the Sha256Hash value of input in hexadecimal format
   176  func HexSha256(value []byte) string {
   177  	return Hex(Sha256Hash(value))
   178  }
   179  
   180  // UrlDecode wrapper of url.QueryUnescape
   181  func UrlDecode(value string) (string, error) {
   182  	ret, err := url.QueryUnescape(value)
   183  	if err == nil {
   184  		return ret, nil
   185  	}
   186  	return "", err
   187  }
   188  
   189  // UrlDecodeWithoutError wrapper of UrlDecode
   190  func UrlDecodeWithoutError(value string) string {
   191  	ret, err := UrlDecode(value)
   192  	if err == nil {
   193  		return ret
   194  	}
   195  	if isErrorLogEnabled() {
   196  		doLog(LEVEL_ERROR, "Url decode error: %v", err)
   197  	}
   198  	return ""
   199  }
   200  
   201  // IsIP checks whether the value matches ip address
   202  func IsIP(value string) bool {
   203  	return ipRegex.MatchString(value)
   204  }
   205  
   206  // UrlEncode encodes the input value
   207  func UrlEncode(value string, chineseOnly bool) string {
   208  	if chineseOnly {
   209  		values := make([]string, 0, len(value))
   210  		for _, val := range value {
   211  			_value := string(val)
   212  			if regex.MatchString(_value) {
   213  				_value = url.QueryEscape(_value)
   214  			}
   215  			values = append(values, _value)
   216  		}
   217  		return strings.Join(values, "")
   218  	}
   219  	return url.QueryEscape(value)
   220  }
   221  
   222  func copyHeaders(m map[string][]string) (ret map[string][]string) {
   223  	if m != nil {
   224  		ret = make(map[string][]string, len(m))
   225  		for key, values := range m {
   226  			_values := make([]string, 0, len(values))
   227  			_values = append(_values, values...)
   228  			ret[strings.ToLower(key)] = _values
   229  		}
   230  	} else {
   231  		ret = make(map[string][]string)
   232  	}
   233  
   234  	return
   235  }
   236  
   237  func parseHeaders(headers map[string][]string) (signature string, region string, signedHeaders string) {
   238  	signature = "v2"
   239  	if receivedAuthorization, ok := headers[strings.ToLower(HEADER_AUTH_CAMEL)]; ok && len(receivedAuthorization) > 0 {
   240  		if strings.HasPrefix(receivedAuthorization[0], V4_HASH_PREFIX) {
   241  			signature = "v4"
   242  			matches := v4AuthRegex.FindStringSubmatch(receivedAuthorization[0])
   243  			if len(matches) >= 3 {
   244  				region = matches[1]
   245  				regions := regionRegex.FindStringSubmatch(region)
   246  				if len(regions) >= 2 {
   247  					region = regions[1]
   248  				}
   249  				signedHeaders = matches[2]
   250  			}
   251  
   252  		} else if strings.HasPrefix(receivedAuthorization[0], V2_HASH_PREFIX) {
   253  			signature = "v2"
   254  		}
   255  	}
   256  	return
   257  }
   258  
   259  func getTemporaryKeys() []string {
   260  	return []string{
   261  		"Signature",
   262  		"signature",
   263  		"X-Amz-Signature",
   264  		"x-amz-signature",
   265  	}
   266  }
   267  
   268  func getIsObs(isTemporary bool, querys []string, headers map[string][]string) bool {
   269  	isObs := true
   270  	if isTemporary {
   271  		for _, value := range querys {
   272  			keyPrefix := strings.ToLower(value)
   273  			if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
   274  				isObs = false
   275  			} else if strings.HasPrefix(value, HEADER_ACCESSS_KEY_AMZ) {
   276  				isObs = false
   277  			}
   278  		}
   279  	} else {
   280  		for key := range headers {
   281  			keyPrefix := strings.ToLower(key)
   282  			if strings.HasPrefix(keyPrefix, HEADER_PREFIX) {
   283  				isObs = false
   284  				break
   285  			}
   286  		}
   287  	}
   288  	return isObs
   289  }
   290  
   291  func isPathStyle(headers map[string][]string, bucketName string) bool {
   292  	if receivedHost, ok := headers[HEADER_HOST]; ok && len(receivedHost) > 0 && !strings.HasPrefix(receivedHost[0], bucketName+".") {
   293  		return true
   294  	}
   295  	return false
   296  }
   297  
   298  // GetV2Authorization v2 Authorization
   299  func GetV2Authorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) {
   300  	queryUrl = strings.TrimPrefix(queryUrl, "?")
   301  
   302  	method = strings.ToUpper(method)
   303  
   304  	querys := strings.Split(queryUrl, "&")
   305  	querysResult := make([]string, 0)
   306  	for _, value := range querys {
   307  		if value != "=" && len(value) != 0 {
   308  			querysResult = append(querysResult, value)
   309  		}
   310  	}
   311  	params := make(map[string]string)
   312  
   313  	for _, value := range querysResult {
   314  		kv := strings.Split(value, "=")
   315  		length := len(kv)
   316  		if length == 1 {
   317  			key := UrlDecodeWithoutError(kv[0])
   318  			params[key] = ""
   319  		} else if length >= 2 {
   320  			key := UrlDecodeWithoutError(kv[0])
   321  			vals := make([]string, 0, length-1)
   322  			for i := 1; i < length; i++ {
   323  				val := UrlDecodeWithoutError(kv[i])
   324  				vals = append(vals, val)
   325  			}
   326  			params[key] = strings.Join(vals, "=")
   327  		}
   328  	}
   329  	headers = copyHeaders(headers)
   330  	pathStyle := isPathStyle(headers, bucketName)
   331  	conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk},
   332  		urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
   333  		pathStyle: pathStyle}
   334  	conf.signature = SignatureObs
   335  	_, canonicalizeURL := conf.formatUrls(bucketName, objectKey, params, false)
   336  	ret = v2Auth(ak, sk, method, canonicalizeURL, headers, true)
   337  	v2HashPrefix := OBS_HASH_PREFIX
   338  	ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
   339  	return
   340  }
   341  
   342  func getQuerysResult(querys []string) []string {
   343  	querysResult := make([]string, 0)
   344  	for _, value := range querys {
   345  		if value != "=" && len(value) != 0 {
   346  			querysResult = append(querysResult, value)
   347  		}
   348  	}
   349  	return querysResult
   350  }
   351  
   352  func getParams(querysResult []string) map[string]string {
   353  	params := make(map[string]string)
   354  	for _, value := range querysResult {
   355  		kv := strings.Split(value, "=")
   356  		length := len(kv)
   357  		if length == 1 {
   358  			key := UrlDecodeWithoutError(kv[0])
   359  			params[key] = ""
   360  		} else if length >= 2 {
   361  			key := UrlDecodeWithoutError(kv[0])
   362  			vals := make([]string, 0, length-1)
   363  			for i := 1; i < length; i++ {
   364  				val := UrlDecodeWithoutError(kv[i])
   365  				vals = append(vals, val)
   366  			}
   367  			params[key] = strings.Join(vals, "=")
   368  		}
   369  	}
   370  	return params
   371  }
   372  
   373  func getTemporaryAndSignature(params map[string]string) (bool, string) {
   374  	isTemporary := false
   375  	signature := "v2"
   376  	temporaryKeys := getTemporaryKeys()
   377  	for _, key := range temporaryKeys {
   378  		if _, ok := params[key]; ok {
   379  			isTemporary = true
   380  			if strings.ToLower(key) == "signature" {
   381  				signature = "v2"
   382  			} else if strings.ToLower(key) == "x-amz-signature" {
   383  				signature = "v4"
   384  			}
   385  			break
   386  		}
   387  	}
   388  	return isTemporary, signature
   389  }
   390  
   391  // GetAuthorization Authorization
   392  func GetAuthorization(ak, sk, method, bucketName, objectKey, queryUrl string, headers map[string][]string) (ret map[string]string) {
   393  	queryUrl = strings.TrimPrefix(queryUrl, "?")
   394  
   395  	method = strings.ToUpper(method)
   396  
   397  	querys := strings.Split(queryUrl, "&")
   398  	querysResult := getQuerysResult(querys)
   399  	params := getParams(querysResult)
   400  
   401  	isTemporary, signature := getTemporaryAndSignature(params)
   402  
   403  	isObs := getIsObs(isTemporary, querysResult, headers)
   404  	headers = copyHeaders(headers)
   405  	pathStyle := false
   406  	if receivedHost, ok := headers[HEADER_HOST]; ok && len(receivedHost) > 0 && !strings.HasPrefix(receivedHost[0], bucketName+".") {
   407  		pathStyle = true
   408  	}
   409  	conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk},
   410  		urlHolder: &urlHolder{scheme: "https", host: "dummy", port: 443},
   411  		pathStyle: pathStyle}
   412  
   413  	if isTemporary {
   414  		return getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature, conf, params, headers, isObs)
   415  	}
   416  	signature, region, signedHeaders := parseHeaders(headers)
   417  	if signature == "v4" {
   418  		conf.signature = SignatureV4
   419  		requestUrl, canonicalizeUrl := conf.formatUrls(bucketName, objectKey, params, false)
   420  		parsedRequestUrl, _err := url.Parse(requestUrl)
   421  		if _err != nil {
   422  			doLog(LEVEL_WARN, "Failed to parse requestUrl with reason: %v", _err)
   423  			return nil
   424  		}
   425  		headerKeys := strings.Split(signedHeaders, ";")
   426  		_headers := make(map[string][]string, len(headerKeys))
   427  		for _, headerKey := range headerKeys {
   428  			_headers[headerKey] = headers[headerKey]
   429  		}
   430  		ret = v4Auth(ak, sk, region, method, canonicalizeUrl, parsedRequestUrl.RawQuery, _headers)
   431  		ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s Credential=%s,SignedHeaders=%s,Signature=%s", V4_HASH_PREFIX, ret["Credential"], ret["SignedHeaders"], ret["Signature"])
   432  	} else if signature == "v2" {
   433  		if isObs {
   434  			conf.signature = SignatureObs
   435  		} else {
   436  			conf.signature = SignatureV2
   437  		}
   438  		_, canonicalizeUrl := conf.formatUrls(bucketName, objectKey, params, false)
   439  		ret = v2Auth(ak, sk, method, canonicalizeUrl, headers, isObs)
   440  		v2HashPrefix := V2_HASH_PREFIX
   441  		if isObs {
   442  			v2HashPrefix = OBS_HASH_PREFIX
   443  		}
   444  		ret[HEADER_AUTH_CAMEL] = fmt.Sprintf("%s %s:%s", v2HashPrefix, ak, ret["Signature"])
   445  	}
   446  	return
   447  
   448  }
   449  
   450  func getTemporaryAuthorization(ak, sk, method, bucketName, objectKey, signature string, conf *config, params map[string]string,
   451  	headers map[string][]string, isObs bool) (ret map[string]string) {
   452  
   453  	if signature == "v4" {
   454  		conf.signature = SignatureV4
   455  
   456  		longDate, ok := params[PARAM_DATE_AMZ_CAMEL]
   457  		if !ok {
   458  			longDate = params[HEADER_DATE_AMZ]
   459  		}
   460  		shortDate := longDate[:8]
   461  
   462  		credential, ok := params[PARAM_CREDENTIAL_AMZ_CAMEL]
   463  		if !ok {
   464  			credential = params[strings.ToLower(PARAM_CREDENTIAL_AMZ_CAMEL)]
   465  		}
   466  
   467  		_credential := UrlDecodeWithoutError(credential)
   468  
   469  		regions := regionRegex.FindStringSubmatch(_credential)
   470  		var region string
   471  		if len(regions) >= 2 {
   472  			region = regions[1]
   473  		}
   474  
   475  		_, scope := getCredential(ak, region, shortDate)
   476  
   477  		expires, ok := params[PARAM_EXPIRES_AMZ_CAMEL]
   478  		if !ok {
   479  			expires = params[strings.ToLower(PARAM_EXPIRES_AMZ_CAMEL)]
   480  		}
   481  
   482  		signedHeaders, ok := params[PARAM_SIGNEDHEADERS_AMZ_CAMEL]
   483  		if !ok {
   484  			signedHeaders = params[strings.ToLower(PARAM_SIGNEDHEADERS_AMZ_CAMEL)]
   485  		}
   486  
   487  		algorithm, ok := params[PARAM_ALGORITHM_AMZ_CAMEL]
   488  		if !ok {
   489  			algorithm = params[strings.ToLower(PARAM_ALGORITHM_AMZ_CAMEL)]
   490  		}
   491  
   492  		delete(params, PARAM_SIGNATURE_AMZ_CAMEL)
   493  		delete(params, strings.ToLower(PARAM_SIGNATURE_AMZ_CAMEL))
   494  
   495  		ret = make(map[string]string, 6)
   496  		ret[PARAM_ALGORITHM_AMZ_CAMEL] = algorithm
   497  		ret[PARAM_CREDENTIAL_AMZ_CAMEL] = credential
   498  		ret[PARAM_DATE_AMZ_CAMEL] = longDate
   499  		ret[PARAM_EXPIRES_AMZ_CAMEL] = expires
   500  		ret[PARAM_SIGNEDHEADERS_AMZ_CAMEL] = signedHeaders
   501  
   502  		requestUrl, canonicalizeUrl := conf.formatUrls(bucketName, objectKey, params, false)
   503  		parsedRequestUrl, _err := url.Parse(requestUrl)
   504  		if _err != nil {
   505  			doLog(LEVEL_WARN, "Failed to parse requestUrl with reason: %v", _err)
   506  			return nil
   507  		}
   508  		stringToSign := getV4StringToSign(method, canonicalizeUrl, parsedRequestUrl.RawQuery, scope, longDate, UNSIGNED_PAYLOAD, strings.Split(signedHeaders, ";"), headers)
   509  		ret[PARAM_SIGNATURE_AMZ_CAMEL] = UrlEncode(getSignature(stringToSign, sk, region, shortDate), false)
   510  	} else if signature == "v2" {
   511  		if isObs {
   512  			conf.signature = SignatureObs
   513  		} else {
   514  			conf.signature = SignatureV2
   515  		}
   516  		_, canonicalizeUrl := conf.formatUrls(bucketName, objectKey, params, false)
   517  		expires, ok := params["Expires"]
   518  		if !ok {
   519  			expires = params["expires"]
   520  		}
   521  		headers[HEADER_DATE_CAMEL] = []string{expires}
   522  		stringToSign := getV2StringToSign(method, canonicalizeUrl, headers, isObs)
   523  		ret = make(map[string]string, 3)
   524  		ret["Signature"] = UrlEncode(Base64Encode(HmacSha1([]byte(sk), []byte(stringToSign))), false)
   525  		ret["AWSAccessKeyId"] = UrlEncode(ak, false)
   526  		ret["Expires"] = UrlEncode(expires, false)
   527  	}
   528  
   529  	return
   530  }