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, "<", "<") 50 srcTmp = StringContains(srcTmp, ">", ">") 51 srcTmp = StringContains(srcTmp, "'", "'") 52 srcTmp = StringContains(srcTmp, "\"", """) 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 }