github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/openstack/obs/convert.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 "encoding/json" 18 "fmt" 19 "io" 20 "io/ioutil" 21 "net/http" 22 "net/url" 23 "reflect" 24 "strings" 25 "time" 26 ) 27 28 func cleanHeaderPrefix(header http.Header) map[string][]string { 29 responseHeaders := make(map[string][]string) 30 for key, value := range header { 31 if len(value) > 0 { 32 key = strings.ToLower(key) 33 if strings.HasPrefix(key, HEADER_PREFIX) || strings.HasPrefix(key, HEADER_PREFIX_OBS) { 34 key = key[len(HEADER_PREFIX):] 35 } 36 responseHeaders[key] = value 37 } 38 } 39 return responseHeaders 40 } 41 42 // ParseStringToEventType converts string value to EventType value and returns it 43 func ParseStringToEventType(value string) (ret EventType) { 44 switch value { 45 case "ObjectCreated:*", "s3:ObjectCreated:*": 46 ret = ObjectCreatedAll 47 case "ObjectCreated:Put", "s3:ObjectCreated:Put": 48 ret = ObjectCreatedPut 49 case "ObjectCreated:Post", "s3:ObjectCreated:Post": 50 ret = ObjectCreatedPost 51 case "ObjectCreated:Copy", "s3:ObjectCreated:Copy": 52 ret = ObjectCreatedCopy 53 case "ObjectCreated:CompleteMultipartUpload", "s3:ObjectCreated:CompleteMultipartUpload": 54 ret = ObjectCreatedCompleteMultipartUpload 55 case "ObjectRemoved:*", "s3:ObjectRemoved:*": 56 ret = ObjectRemovedAll 57 case "ObjectRemoved:Delete", "s3:ObjectRemoved:Delete": 58 ret = ObjectRemovedDelete 59 case "ObjectRemoved:DeleteMarkerCreated", "s3:ObjectRemoved:DeleteMarkerCreated": 60 ret = ObjectRemovedDeleteMarkerCreated 61 default: 62 ret = "" 63 } 64 return 65 } 66 67 // ParseStringToStorageClassType converts string value to StorageClassType value and returns it 68 func ParseStringToStorageClassType(value string) (ret StorageClassType) { 69 switch value { 70 case "STANDARD": 71 ret = StorageClassStandard 72 case "STANDARD_IA", "WARM": 73 ret = StorageClassWarm 74 case "GLACIER", "COLD": 75 ret = StorageClassCold 76 default: 77 ret = "" 78 } 79 return 80 } 81 82 func prepareGrantURI(grant Grant) string { 83 if grant.Grantee.URI == GroupAllUsers || grant.Grantee.URI == GroupAuthenticatedUsers { 84 return fmt.Sprintf("<URI>%s%s</URI>", "http://acs.amazonaws.com/groups/global/", grant.Grantee.URI) 85 } 86 if grant.Grantee.URI == GroupLogDelivery { 87 return fmt.Sprintf("<URI>%s%s</URI>", "http://acs.amazonaws.com/groups/s3/", grant.Grantee.URI) 88 } 89 return fmt.Sprintf("<URI>%s</URI>", grant.Grantee.URI) 90 } 91 92 func convertGrantToXML(grant Grant, isObs bool, isBucket bool) string { 93 xml := make([]string, 0, 4) 94 95 if grant.Grantee.Type == GranteeUser { 96 if isObs { 97 xml = append(xml, "<Grant><Grantee>") 98 } else { 99 xml = append(xml, fmt.Sprintf("<Grant><Grantee xsi:type=\"%s\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">", grant.Grantee.Type)) 100 } 101 if grant.Grantee.ID != "" { 102 granteeID := XmlTranscoding(grant.Grantee.ID) 103 xml = append(xml, fmt.Sprintf("<ID>%s</ID>", granteeID)) 104 } 105 if !isObs && grant.Grantee.DisplayName != "" { 106 granteeDisplayName := XmlTranscoding(grant.Grantee.DisplayName) 107 xml = append(xml, fmt.Sprintf("<DisplayName>%s</DisplayName>", granteeDisplayName)) 108 } 109 xml = append(xml, "</Grantee>") 110 } else { 111 if !isObs { 112 xml = append(xml, fmt.Sprintf("<Grant><Grantee xsi:type=\"%s\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">", grant.Grantee.Type)) 113 xml = append(xml, prepareGrantURI(grant)) 114 xml = append(xml, "</Grantee>") 115 } else if grant.Grantee.URI == GroupAllUsers { 116 xml = append(xml, "<Grant><Grantee>") 117 xml = append(xml, fmt.Sprintf("<Canned>Everyone</Canned>")) 118 xml = append(xml, "</Grantee>") 119 } else { 120 return strings.Join(xml, "") 121 } 122 } 123 124 xml = append(xml, fmt.Sprintf("<Permission>%s</Permission>", grant.Permission)) 125 if isObs && isBucket { 126 xml = append(xml, fmt.Sprintf("<Delivered>%t</Delivered>", grant.Delivered)) 127 } 128 xml = append(xml, fmt.Sprintf("</Grant>")) 129 return strings.Join(xml, "") 130 } 131 132 func hasLoggingTarget(input BucketLoggingStatus) bool { 133 if input.TargetBucket != "" || input.TargetPrefix != "" || len(input.TargetGrants) > 0 { 134 return true 135 } 136 return false 137 } 138 139 // ConvertLoggingStatusToXml converts BucketLoggingStatus value to XML data and returns it 140 func ConvertLoggingStatusToXml(input BucketLoggingStatus, returnMd5 bool, isObs bool) (data string, md5 string) { 141 grantsLength := len(input.TargetGrants) 142 xml := make([]string, 0, 8+grantsLength) 143 144 xml = append(xml, "<BucketLoggingStatus>") 145 if isObs && input.Agency != "" { 146 agency := XmlTranscoding(input.Agency) 147 xml = append(xml, fmt.Sprintf("<Agency>%s</Agency>", agency)) 148 } 149 if hasLoggingTarget(input) { 150 xml = append(xml, "<LoggingEnabled>") 151 if input.TargetBucket != "" { 152 xml = append(xml, fmt.Sprintf("<TargetBucket>%s</TargetBucket>", input.TargetBucket)) 153 } 154 if input.TargetPrefix != "" { 155 targetPrefix := XmlTranscoding(input.TargetPrefix) 156 xml = append(xml, fmt.Sprintf("<TargetPrefix>%s</TargetPrefix>", targetPrefix)) 157 } 158 if grantsLength > 0 { 159 xml = append(xml, "<TargetGrants>") 160 for _, grant := range input.TargetGrants { 161 xml = append(xml, convertGrantToXML(grant, isObs, false)) 162 } 163 xml = append(xml, "</TargetGrants>") 164 } 165 166 xml = append(xml, "</LoggingEnabled>") 167 } 168 xml = append(xml, "</BucketLoggingStatus>") 169 data = strings.Join(xml, "") 170 if returnMd5 { 171 md5 = Base64Md5([]byte(data)) 172 } 173 return 174 } 175 176 // ConvertAclToXml converts AccessControlPolicy value to XML data and returns it 177 func ConvertAclToXml(input AccessControlPolicy, returnMd5 bool, isObs bool) (data string, md5 string) { 178 xml := make([]string, 0, 4+len(input.Grants)) 179 ownerID := XmlTranscoding(input.Owner.ID) 180 xml = append(xml, fmt.Sprintf("<AccessControlPolicy><Owner><ID>%s</ID>", ownerID)) 181 if !isObs && input.Owner.DisplayName != "" { 182 ownerDisplayName := XmlTranscoding(input.Owner.DisplayName) 183 xml = append(xml, fmt.Sprintf("<DisplayName>%s</DisplayName>", ownerDisplayName)) 184 } 185 if isObs && input.Delivered != "" { 186 objectDelivered := XmlTranscoding(input.Delivered) 187 xml = append(xml, fmt.Sprintf("</Owner><Delivered>%s</Delivered><AccessControlList>", objectDelivered)) 188 } else { 189 xml = append(xml, "</Owner><AccessControlList>") 190 } 191 for _, grant := range input.Grants { 192 xml = append(xml, convertGrantToXML(grant, isObs, false)) 193 } 194 xml = append(xml, "</AccessControlList></AccessControlPolicy>") 195 data = strings.Join(xml, "") 196 if returnMd5 { 197 md5 = Base64Md5([]byte(data)) 198 } 199 return 200 } 201 202 func convertBucketACLToXML(input AccessControlPolicy, returnMd5 bool, isObs bool) (data string, md5 string) { 203 xml := make([]string, 0, 4+len(input.Grants)) 204 ownerID := XmlTranscoding(input.Owner.ID) 205 xml = append(xml, fmt.Sprintf("<AccessControlPolicy><Owner><ID>%s</ID>", ownerID)) 206 if !isObs && input.Owner.DisplayName != "" { 207 ownerDisplayName := XmlTranscoding(input.Owner.DisplayName) 208 xml = append(xml, fmt.Sprintf("<DisplayName>%s</DisplayName>", ownerDisplayName)) 209 } 210 211 xml = append(xml, "</Owner><AccessControlList>") 212 213 for _, grant := range input.Grants { 214 xml = append(xml, convertGrantToXML(grant, isObs, true)) 215 } 216 xml = append(xml, "</AccessControlList></AccessControlPolicy>") 217 data = strings.Join(xml, "") 218 if returnMd5 { 219 md5 = Base64Md5([]byte(data)) 220 } 221 return 222 } 223 224 func convertConditionToXML(condition Condition) string { 225 xml := make([]string, 0, 2) 226 if condition.KeyPrefixEquals != "" { 227 keyPrefixEquals := XmlTranscoding(condition.KeyPrefixEquals) 228 xml = append(xml, fmt.Sprintf("<KeyPrefixEquals>%s</KeyPrefixEquals>", keyPrefixEquals)) 229 } 230 if condition.HttpErrorCodeReturnedEquals != "" { 231 xml = append(xml, fmt.Sprintf("<HttpErrorCodeReturnedEquals>%s</HttpErrorCodeReturnedEquals>", condition.HttpErrorCodeReturnedEquals)) 232 } 233 if len(xml) > 0 { 234 return fmt.Sprintf("<Condition>%s</Condition>", strings.Join(xml, "")) 235 } 236 return "" 237 } 238 239 func prepareRoutingRule(input BucketWebsiteConfiguration) string { 240 xml := make([]string, 0, len(input.RoutingRules)*10) 241 for _, routingRule := range input.RoutingRules { 242 xml = append(xml, "<RoutingRule>") 243 xml = append(xml, "<Redirect>") 244 if routingRule.Redirect.Protocol != "" { 245 xml = append(xml, fmt.Sprintf("<Protocol>%s</Protocol>", routingRule.Redirect.Protocol)) 246 } 247 if routingRule.Redirect.HostName != "" { 248 xml = append(xml, fmt.Sprintf("<HostName>%s</HostName>", routingRule.Redirect.HostName)) 249 } 250 if routingRule.Redirect.ReplaceKeyPrefixWith != "" { 251 replaceKeyPrefixWith := XmlTranscoding(routingRule.Redirect.ReplaceKeyPrefixWith) 252 xml = append(xml, fmt.Sprintf("<ReplaceKeyPrefixWith>%s</ReplaceKeyPrefixWith>", replaceKeyPrefixWith)) 253 } 254 255 if routingRule.Redirect.ReplaceKeyWith != "" { 256 replaceKeyWith := XmlTranscoding(routingRule.Redirect.ReplaceKeyWith) 257 xml = append(xml, fmt.Sprintf("<ReplaceKeyWith>%s</ReplaceKeyWith>", replaceKeyWith)) 258 } 259 if routingRule.Redirect.HttpRedirectCode != "" { 260 xml = append(xml, fmt.Sprintf("<HttpRedirectCode>%s</HttpRedirectCode>", routingRule.Redirect.HttpRedirectCode)) 261 } 262 xml = append(xml, "</Redirect>") 263 264 if ret := convertConditionToXML(routingRule.Condition); ret != "" { 265 xml = append(xml, ret) 266 } 267 xml = append(xml, "</RoutingRule>") 268 } 269 return strings.Join(xml, "") 270 } 271 272 // ConvertWebsiteConfigurationToXml converts BucketWebsiteConfiguration value to XML data and returns it 273 func ConvertWebsiteConfigurationToXml(input BucketWebsiteConfiguration, returnMd5 bool) (data string, md5 string) { 274 routingRuleLength := len(input.RoutingRules) 275 xml := make([]string, 0, 6+routingRuleLength*10) 276 xml = append(xml, "<WebsiteConfiguration>") 277 278 if input.RedirectAllRequestsTo.HostName != "" { 279 xml = append(xml, fmt.Sprintf("<RedirectAllRequestsTo><HostName>%s</HostName>", input.RedirectAllRequestsTo.HostName)) 280 if input.RedirectAllRequestsTo.Protocol != "" { 281 xml = append(xml, fmt.Sprintf("<Protocol>%s</Protocol>", input.RedirectAllRequestsTo.Protocol)) 282 } 283 xml = append(xml, "</RedirectAllRequestsTo>") 284 } else { 285 if input.IndexDocument.Suffix != "" { 286 indexDocumentSuffix := XmlTranscoding(input.IndexDocument.Suffix) 287 xml = append(xml, fmt.Sprintf("<IndexDocument><Suffix>%s</Suffix></IndexDocument>", indexDocumentSuffix)) 288 } 289 if input.ErrorDocument.Key != "" { 290 errorDocumentKey := XmlTranscoding(input.ErrorDocument.Key) 291 xml = append(xml, fmt.Sprintf("<ErrorDocument><Key>%s</Key></ErrorDocument>", errorDocumentKey)) 292 } 293 if routingRuleLength > 0 { 294 xml = append(xml, "<RoutingRules>") 295 xml = append(xml, prepareRoutingRule(input)) 296 xml = append(xml, "</RoutingRules>") 297 } 298 } 299 300 xml = append(xml, "</WebsiteConfiguration>") 301 data = strings.Join(xml, "") 302 if returnMd5 { 303 md5 = Base64Md5([]byte(data)) 304 } 305 return 306 } 307 308 func convertTransitionsToXML(transitions []Transition, isObs bool) string { 309 if length := len(transitions); length > 0 { 310 xml := make([]string, 0, length) 311 for _, transition := range transitions { 312 var temp string 313 if transition.Days > 0 { 314 temp = fmt.Sprintf("<Days>%d</Days>", transition.Days) 315 } else if !transition.Date.IsZero() { 316 temp = fmt.Sprintf("<Date>%s</Date>", transition.Date.UTC().Format(ISO8601_MIDNIGHT_DATE_FORMAT)) 317 } 318 if temp != "" { 319 if !isObs { 320 storageClass := string(transition.StorageClass) 321 if transition.StorageClass == StorageClassWarm { 322 storageClass = string(storageClassStandardIA) 323 } else if transition.StorageClass == StorageClassCold { 324 storageClass = string(storageClassGlacier) 325 } 326 xml = append(xml, fmt.Sprintf("<Transition>%s<StorageClass>%s</StorageClass></Transition>", temp, storageClass)) 327 } else { 328 xml = append(xml, fmt.Sprintf("<Transition>%s<StorageClass>%s</StorageClass></Transition>", temp, transition.StorageClass)) 329 } 330 } 331 } 332 return strings.Join(xml, "") 333 } 334 return "" 335 } 336 337 func convertExpirationToXML(expiration Expiration) string { 338 if expiration.Days > 0 { 339 return fmt.Sprintf("<Expiration><Days>%d</Days></Expiration>", expiration.Days) 340 } else if !expiration.Date.IsZero() { 341 return fmt.Sprintf("<Expiration><Date>%s</Date></Expiration>", expiration.Date.UTC().Format(ISO8601_MIDNIGHT_DATE_FORMAT)) 342 } 343 return "" 344 } 345 func convertNoncurrentVersionTransitionsToXML(noncurrentVersionTransitions []NoncurrentVersionTransition, isObs bool) string { 346 if length := len(noncurrentVersionTransitions); length > 0 { 347 xml := make([]string, 0, length) 348 for _, noncurrentVersionTransition := range noncurrentVersionTransitions { 349 if noncurrentVersionTransition.NoncurrentDays > 0 { 350 storageClass := string(noncurrentVersionTransition.StorageClass) 351 if !isObs { 352 if storageClass == string(StorageClassWarm) { 353 storageClass = string(storageClassStandardIA) 354 } else if storageClass == string(StorageClassCold) { 355 storageClass = string(storageClassGlacier) 356 } 357 } 358 xml = append(xml, fmt.Sprintf("<NoncurrentVersionTransition><NoncurrentDays>%d</NoncurrentDays>"+ 359 "<StorageClass>%s</StorageClass></NoncurrentVersionTransition>", 360 noncurrentVersionTransition.NoncurrentDays, storageClass)) 361 } 362 } 363 return strings.Join(xml, "") 364 } 365 return "" 366 } 367 func convertNoncurrentVersionExpirationToXML(noncurrentVersionExpiration NoncurrentVersionExpiration) string { 368 if noncurrentVersionExpiration.NoncurrentDays > 0 { 369 return fmt.Sprintf("<NoncurrentVersionExpiration><NoncurrentDays>%d</NoncurrentDays></NoncurrentVersionExpiration>", noncurrentVersionExpiration.NoncurrentDays) 370 } 371 return "" 372 } 373 374 // ConvertLifecyleConfigurationToXml converts BucketLifecyleConfiguration value to XML data and returns it 375 func ConvertLifecyleConfigurationToXml(input BucketLifecyleConfiguration, returnMd5 bool, isObs bool) (data string, md5 string) { 376 xml := make([]string, 0, 2+len(input.LifecycleRules)*9) 377 xml = append(xml, "<LifecycleConfiguration>") 378 for _, lifecyleRule := range input.LifecycleRules { 379 xml = append(xml, "<Rule>") 380 if lifecyleRule.ID != "" { 381 lifecyleRuleID := XmlTranscoding(lifecyleRule.ID) 382 xml = append(xml, fmt.Sprintf("<ID>%s</ID>", lifecyleRuleID)) 383 } 384 lifecyleRulePrefix := XmlTranscoding(lifecyleRule.Prefix) 385 xml = append(xml, fmt.Sprintf("<Prefix>%s</Prefix>", lifecyleRulePrefix)) 386 xml = append(xml, fmt.Sprintf("<Status>%s</Status>", lifecyleRule.Status)) 387 if ret := convertTransitionsToXML(lifecyleRule.Transitions, isObs); ret != "" { 388 xml = append(xml, ret) 389 } 390 if ret := convertExpirationToXML(lifecyleRule.Expiration); ret != "" { 391 xml = append(xml, ret) 392 } 393 if ret := convertNoncurrentVersionTransitionsToXML(lifecyleRule.NoncurrentVersionTransitions, isObs); ret != "" { 394 xml = append(xml, ret) 395 } 396 if ret := convertNoncurrentVersionExpirationToXML(lifecyleRule.NoncurrentVersionExpiration); ret != "" { 397 xml = append(xml, ret) 398 } 399 xml = append(xml, "</Rule>") 400 } 401 xml = append(xml, "</LifecycleConfiguration>") 402 data = strings.Join(xml, "") 403 if returnMd5 { 404 md5 = Base64Md5([]byte(data)) 405 } 406 return 407 } 408 409 // ConvertEncryptionConfigurationToXml converts BucketEncryptionConfiguration value to XML data and returns it 410 func ConvertEncryptionConfigurationToXml(input BucketEncryptionConfiguration, returnMd5 bool, isObs bool) (data string, md5 string) { 411 xml := make([]string, 0, 5) 412 xml = append(xml, "<ServerSideEncryptionConfiguration><Rule><ApplyServerSideEncryptionByDefault>") 413 414 algorithm := XmlTranscoding(input.SSEAlgorithm) 415 xml = append(xml, fmt.Sprintf("<SSEAlgorithm>%s</SSEAlgorithm>", algorithm)) 416 417 if input.KMSMasterKeyID != "" { 418 kmsKeyID := XmlTranscoding(input.KMSMasterKeyID) 419 xml = append(xml, fmt.Sprintf("<KMSMasterKeyID>%s</KMSMasterKeyID>", kmsKeyID)) 420 } 421 if input.ProjectID != "" { 422 projectID := XmlTranscoding(input.ProjectID) 423 xml = append(xml, fmt.Sprintf("<ProjectID>%s</ProjectID>", projectID)) 424 } 425 426 xml = append(xml, "</ApplyServerSideEncryptionByDefault></Rule></ServerSideEncryptionConfiguration>") 427 data = strings.Join(xml, "") 428 if returnMd5 { 429 md5 = Base64Md5([]byte(data)) 430 } 431 return 432 } 433 434 func converntFilterRulesToXML(filterRules []FilterRule, isObs bool) string { 435 if length := len(filterRules); length > 0 { 436 xml := make([]string, 0, length*4) 437 for _, filterRule := range filterRules { 438 xml = append(xml, "<FilterRule>") 439 if filterRule.Name != "" { 440 filterRuleName := XmlTranscoding(filterRule.Name) 441 xml = append(xml, fmt.Sprintf("<Name>%s</Name>", filterRuleName)) 442 } 443 if filterRule.Value != "" { 444 filterRuleValue := XmlTranscoding(filterRule.Value) 445 xml = append(xml, fmt.Sprintf("<Value>%s</Value>", filterRuleValue)) 446 } 447 xml = append(xml, "</FilterRule>") 448 } 449 if !isObs { 450 return fmt.Sprintf("<Filter><S3Key>%s</S3Key></Filter>", strings.Join(xml, "")) 451 } 452 return fmt.Sprintf("<Filter><Object>%s</Object></Filter>", strings.Join(xml, "")) 453 } 454 return "" 455 } 456 457 func converntEventsToXML(events []EventType, isObs bool) string { 458 if length := len(events); length > 0 { 459 xml := make([]string, 0, length) 460 if !isObs { 461 for _, event := range events { 462 xml = append(xml, fmt.Sprintf("<Event>%s%s</Event>", "s3:", event)) 463 } 464 } else { 465 for _, event := range events { 466 xml = append(xml, fmt.Sprintf("<Event>%s</Event>", event)) 467 } 468 } 469 return strings.Join(xml, "") 470 } 471 return "" 472 } 473 474 func converntConfigureToXML(topicConfiguration TopicConfiguration, xmlElem string, isObs bool) string { 475 xml := make([]string, 0, 6) 476 xml = append(xml, xmlElem) 477 if topicConfiguration.ID != "" { 478 topicConfigurationID := XmlTranscoding(topicConfiguration.ID) 479 xml = append(xml, fmt.Sprintf("<Id>%s</Id>", topicConfigurationID)) 480 } 481 topicConfigurationTopic := XmlTranscoding(topicConfiguration.Topic) 482 xml = append(xml, fmt.Sprintf("<Topic>%s</Topic>", topicConfigurationTopic)) 483 484 if ret := converntEventsToXML(topicConfiguration.Events, isObs); ret != "" { 485 xml = append(xml, ret) 486 } 487 if ret := converntFilterRulesToXML(topicConfiguration.FilterRules, isObs); ret != "" { 488 xml = append(xml, ret) 489 } 490 tempElem := xmlElem[0:1] + "/" + xmlElem[1:] 491 xml = append(xml, tempElem) 492 return strings.Join(xml, "") 493 } 494 495 // ConverntObsRestoreToXml converts RestoreObjectInput value to XML data and returns it 496 func ConverntObsRestoreToXml(restoreObjectInput RestoreObjectInput) string { 497 xml := make([]string, 0, 2) 498 xml = append(xml, fmt.Sprintf("<RestoreRequest><Days>%d</Days>", restoreObjectInput.Days)) 499 if restoreObjectInput.Tier != "Bulk" { 500 xml = append(xml, fmt.Sprintf("<RestoreJob><Tier>%s</Tier></RestoreJob>", restoreObjectInput.Tier)) 501 } 502 xml = append(xml, fmt.Sprintf("</RestoreRequest>")) 503 data := strings.Join(xml, "") 504 return data 505 } 506 507 // ConvertNotificationToXml converts BucketNotification value to XML data and returns it 508 func ConvertNotificationToXml(input BucketNotification, returnMd5 bool, isObs bool) (data string, md5 string) { 509 xml := make([]string, 0, 2+len(input.TopicConfigurations)*6) 510 xml = append(xml, "<NotificationConfiguration>") 511 for _, topicConfiguration := range input.TopicConfigurations { 512 ret := converntConfigureToXML(topicConfiguration, "<TopicConfiguration>", isObs) 513 xml = append(xml, ret) 514 } 515 xml = append(xml, "</NotificationConfiguration>") 516 data = strings.Join(xml, "") 517 if returnMd5 { 518 md5 = Base64Md5([]byte(data)) 519 } 520 return 521 } 522 523 // ConvertCompleteMultipartUploadInputToXml converts CompleteMultipartUploadInput value to XML data and returns it 524 func ConvertCompleteMultipartUploadInputToXml(input CompleteMultipartUploadInput, returnMd5 bool) (data string, md5 string) { 525 xml := make([]string, 0, 2+len(input.Parts)*4) 526 xml = append(xml, "<CompleteMultipartUpload>") 527 for _, part := range input.Parts { 528 xml = append(xml, "<Part>") 529 xml = append(xml, fmt.Sprintf("<PartNumber>%d</PartNumber>", part.PartNumber)) 530 xml = append(xml, fmt.Sprintf("<ETag>%s</ETag>", part.ETag)) 531 xml = append(xml, "</Part>") 532 } 533 xml = append(xml, "</CompleteMultipartUpload>") 534 data = strings.Join(xml, "") 535 if returnMd5 { 536 md5 = Base64Md5([]byte(data)) 537 } 538 return 539 } 540 541 func convertDeleteObjectsToXML(input DeleteObjectsInput) (data string, md5 string) { 542 xml := make([]string, 0, 4+len(input.Objects)*4) 543 xml = append(xml, "<Delete>") 544 if input.Quiet { 545 xml = append(xml, fmt.Sprintf("<Quiet>%t</Quiet>", input.Quiet)) 546 } 547 if input.EncodingType != "" { 548 encodingType := XmlTranscoding(input.EncodingType) 549 xml = append(xml, fmt.Sprintf("<EncodingType>%s</EncodingType>", encodingType)) 550 } 551 for _, obj := range input.Objects { 552 xml = append(xml, "<Object>") 553 key := XmlTranscoding(obj.Key) 554 xml = append(xml, fmt.Sprintf("<Key>%s</Key>", key)) 555 if obj.VersionId != "" { 556 xml = append(xml, fmt.Sprintf("<VersionId>%s</VersionId>", obj.VersionId)) 557 } 558 xml = append(xml, "</Object>") 559 } 560 xml = append(xml, "</Delete>") 561 data = strings.Join(xml, "") 562 md5 = Base64Md5([]byte(data)) 563 return 564 } 565 566 func parseSseHeader(responseHeaders map[string][]string) (sseHeader ISseHeader) { 567 if ret, ok := responseHeaders[HEADER_SSEC_ENCRYPTION]; ok { 568 sseCHeader := SseCHeader{Encryption: ret[0]} 569 if ret, ok = responseHeaders[HEADER_SSEC_KEY_MD5]; ok { 570 sseCHeader.KeyMD5 = ret[0] 571 } 572 sseHeader = sseCHeader 573 } else if ret, ok := responseHeaders[HEADER_SSEKMS_ENCRYPTION]; ok { 574 sseKmsHeader := SseKmsHeader{Encryption: ret[0]} 575 if ret, ok = responseHeaders[HEADER_SSEKMS_KEY]; ok { 576 sseKmsHeader.Key = ret[0] 577 } else if ret, ok = responseHeaders[HEADER_SSEKMS_ENCRYPT_KEY_OBS]; ok { 578 sseKmsHeader.Key = ret[0] 579 } 580 sseHeader = sseKmsHeader 581 } 582 return 583 } 584 585 func parseCorsHeader(output BaseModel) (AllowOrigin, AllowHeader, AllowMethod, ExposeHeader string, MaxAgeSeconds int) { 586 if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_ORIGIN]; ok { 587 AllowOrigin = ret[0] 588 } 589 if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_HEADERS]; ok { 590 AllowHeader = ret[0] 591 } 592 if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_MAX_AGE]; ok { 593 MaxAgeSeconds = StringToInt(ret[0], 0) 594 } 595 if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_ALLOW_METHODS]; ok { 596 AllowMethod = ret[0] 597 } 598 if ret, ok := output.ResponseHeaders[HEADER_ACCESS_CONRTOL_EXPOSE_HEADERS]; ok { 599 ExposeHeader = ret[0] 600 } 601 return 602 } 603 604 func parseUnCommonHeader(output *GetObjectMetadataOutput) { 605 if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok { 606 output.VersionId = ret[0] 607 } 608 if ret, ok := output.ResponseHeaders[HEADER_WEBSITE_REDIRECT_LOCATION]; ok { 609 output.WebsiteRedirectLocation = ret[0] 610 } 611 if ret, ok := output.ResponseHeaders[HEADER_EXPIRATION]; ok { 612 output.Expiration = ret[0] 613 } 614 if ret, ok := output.ResponseHeaders[HEADER_RESTORE]; ok { 615 output.Restore = ret[0] 616 } 617 if ret, ok := output.ResponseHeaders[HEADER_OBJECT_TYPE]; ok { 618 output.ObjectType = ret[0] 619 } 620 if ret, ok := output.ResponseHeaders[HEADER_NEXT_APPEND_POSITION]; ok { 621 output.NextAppendPosition = ret[0] 622 } 623 } 624 625 // ParseGetObjectMetadataOutput sets GetObjectMetadataOutput field values with response headers 626 func ParseGetObjectMetadataOutput(output *GetObjectMetadataOutput) { 627 output.AllowOrigin, output.AllowHeader, output.AllowMethod, output.ExposeHeader, output.MaxAgeSeconds = parseCorsHeader(output.BaseModel) 628 parseUnCommonHeader(output) 629 if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok { 630 output.StorageClass = ParseStringToStorageClassType(ret[0]) 631 } 632 if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok { 633 output.ETag = ret[0] 634 } 635 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_TYPE]; ok { 636 output.ContentType = ret[0] 637 } 638 639 output.SseHeader = parseSseHeader(output.ResponseHeaders) 640 if ret, ok := output.ResponseHeaders[HEADER_LASTMODIFIED]; ok { 641 ret, err := time.Parse(time.RFC1123, ret[0]) 642 if err == nil { 643 output.LastModified = ret 644 } 645 } 646 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LENGTH]; ok { 647 output.ContentLength = StringToInt64(ret[0], 0) 648 } 649 650 output.Metadata = make(map[string]string) 651 652 for key, value := range output.ResponseHeaders { 653 if strings.HasPrefix(key, PREFIX_META) { 654 _key := key[len(PREFIX_META):] 655 output.ResponseHeaders[_key] = value 656 output.Metadata[_key] = value[0] 657 delete(output.ResponseHeaders, key) 658 } 659 } 660 661 } 662 663 // ParseCopyObjectOutput sets CopyObjectOutput field values with response headers 664 func ParseCopyObjectOutput(output *CopyObjectOutput) { 665 if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok { 666 output.VersionId = ret[0] 667 } 668 output.SseHeader = parseSseHeader(output.ResponseHeaders) 669 if ret, ok := output.ResponseHeaders[HEADER_COPY_SOURCE_VERSION_ID]; ok { 670 output.CopySourceVersionId = ret[0] 671 } 672 } 673 674 // ParsePutObjectOutput sets PutObjectOutput field values with response headers 675 func ParsePutObjectOutput(output *PutObjectOutput) { 676 if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok { 677 output.VersionId = ret[0] 678 } 679 output.SseHeader = parseSseHeader(output.ResponseHeaders) 680 if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok { 681 output.StorageClass = ParseStringToStorageClassType(ret[0]) 682 } 683 if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok { 684 output.ETag = ret[0] 685 } 686 } 687 688 // ParseInitiateMultipartUploadOutput sets InitiateMultipartUploadOutput field values with response headers 689 func ParseInitiateMultipartUploadOutput(output *InitiateMultipartUploadOutput) { 690 output.SseHeader = parseSseHeader(output.ResponseHeaders) 691 } 692 693 // ParseUploadPartOutput sets UploadPartOutput field values with response headers 694 func ParseUploadPartOutput(output *UploadPartOutput) { 695 output.SseHeader = parseSseHeader(output.ResponseHeaders) 696 if ret, ok := output.ResponseHeaders[HEADER_ETAG]; ok { 697 output.ETag = ret[0] 698 } 699 } 700 701 // ParseCompleteMultipartUploadOutput sets CompleteMultipartUploadOutput field values with response headers 702 func ParseCompleteMultipartUploadOutput(output *CompleteMultipartUploadOutput) { 703 output.SseHeader = parseSseHeader(output.ResponseHeaders) 704 if ret, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok { 705 output.VersionId = ret[0] 706 } 707 } 708 709 // ParseCopyPartOutput sets CopyPartOutput field values with response headers 710 func ParseCopyPartOutput(output *CopyPartOutput) { 711 output.SseHeader = parseSseHeader(output.ResponseHeaders) 712 } 713 714 // ParseGetBucketMetadataOutput sets GetBucketMetadataOutput field values with response headers 715 func ParseGetBucketMetadataOutput(output *GetBucketMetadataOutput) { 716 output.AllowOrigin, output.AllowHeader, output.AllowMethod, output.ExposeHeader, output.MaxAgeSeconds = parseCorsHeader(output.BaseModel) 717 if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS]; ok { 718 output.StorageClass = ParseStringToStorageClassType(ret[0]) 719 } else if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok { 720 output.StorageClass = ParseStringToStorageClassType(ret[0]) 721 } 722 if ret, ok := output.ResponseHeaders[HEADER_VERSION_OBS]; ok { 723 output.Version = ret[0] 724 } 725 if ret, ok := output.ResponseHeaders[HEADER_BUCKET_REGION]; ok { 726 output.Location = ret[0] 727 } else if ret, ok := output.ResponseHeaders[HEADER_BUCKET_LOCATION_OBS]; ok { 728 output.Location = ret[0] 729 } 730 if ret, ok := output.ResponseHeaders[HEADER_EPID_HEADERS]; ok { 731 output.Epid = ret[0] 732 } 733 if ret, ok := output.ResponseHeaders[HEADER_AZ_REDUNDANCY]; ok { 734 output.AvailableZone = ret[0] 735 } 736 if ret, ok := output.ResponseHeaders[headerFSFileInterface]; ok { 737 output.FSStatus = parseStringToFSStatusType(ret[0]) 738 } else { 739 output.FSStatus = FSStatusDisabled 740 } 741 } 742 743 func parseContentHeader(output *SetObjectMetadataOutput) { 744 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_DISPOSITION]; ok { 745 output.ContentDisposition = ret[0] 746 } 747 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_ENCODING]; ok { 748 output.ContentEncoding = ret[0] 749 } 750 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LANGUAGE]; ok { 751 output.ContentLanguage = ret[0] 752 } 753 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_TYPE]; ok { 754 output.ContentType = ret[0] 755 } 756 } 757 758 // ParseSetObjectMetadataOutput sets SetObjectMetadataOutput field values with response headers 759 func ParseSetObjectMetadataOutput(output *SetObjectMetadataOutput) { 760 if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS]; ok { 761 output.StorageClass = ParseStringToStorageClassType(ret[0]) 762 } else if ret, ok := output.ResponseHeaders[HEADER_STORAGE_CLASS2]; ok { 763 output.StorageClass = ParseStringToStorageClassType(ret[0]) 764 } 765 if ret, ok := output.ResponseHeaders[HEADER_METADATA_DIRECTIVE]; ok { 766 output.MetadataDirective = MetadataDirectiveType(ret[0]) 767 } 768 if ret, ok := output.ResponseHeaders[HEADER_CACHE_CONTROL]; ok { 769 output.CacheControl = ret[0] 770 } 771 parseContentHeader(output) 772 if ret, ok := output.ResponseHeaders[HEADER_EXPIRES]; ok { 773 output.Expires = ret[0] 774 } 775 if ret, ok := output.ResponseHeaders[HEADER_WEBSITE_REDIRECT_LOCATION]; ok { 776 output.WebsiteRedirectLocation = ret[0] 777 } 778 output.Metadata = make(map[string]string) 779 780 for key, value := range output.ResponseHeaders { 781 if strings.HasPrefix(key, PREFIX_META) { 782 _key := key[len(PREFIX_META):] 783 output.ResponseHeaders[_key] = value 784 output.Metadata[_key] = value[0] 785 delete(output.ResponseHeaders, key) 786 } 787 } 788 } 789 790 // ParseDeleteObjectOutput sets DeleteObjectOutput field values with response headers 791 func ParseDeleteObjectOutput(output *DeleteObjectOutput) { 792 if versionID, ok := output.ResponseHeaders[HEADER_VERSION_ID]; ok { 793 output.VersionId = versionID[0] 794 } 795 796 if deleteMarker, ok := output.ResponseHeaders[HEADER_DELETE_MARKER]; ok { 797 output.DeleteMarker = deleteMarker[0] == "true" 798 } 799 } 800 801 // ParseGetObjectOutput sets GetObjectOutput field values with response headers 802 func ParseGetObjectOutput(output *GetObjectOutput) { 803 ParseGetObjectMetadataOutput(&output.GetObjectMetadataOutput) 804 if ret, ok := output.ResponseHeaders[HEADER_DELETE_MARKER]; ok { 805 output.DeleteMarker = ret[0] == "true" 806 } 807 if ret, ok := output.ResponseHeaders[HEADER_CACHE_CONTROL]; ok { 808 output.CacheControl = ret[0] 809 } 810 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_DISPOSITION]; ok { 811 output.ContentDisposition = ret[0] 812 } 813 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_ENCODING]; ok { 814 output.ContentEncoding = ret[0] 815 } 816 if ret, ok := output.ResponseHeaders[HEADER_CONTENT_LANGUAGE]; ok { 817 output.ContentLanguage = ret[0] 818 } 819 if ret, ok := output.ResponseHeaders[HEADER_EXPIRES]; ok { 820 output.Expires = ret[0] 821 } 822 } 823 824 // ConvertRequestToIoReaderV2 converts req to XML data 825 func ConvertRequestToIoReaderV2(req interface{}) (io.Reader, string, error) { 826 data, err := TransToXml(req) 827 if err == nil { 828 if isDebugLogEnabled() { 829 doLog(LEVEL_DEBUG, "Do http request with data: %s", string(data)) 830 } 831 return bytes.NewReader(data), Base64Md5(data), nil 832 } 833 return nil, "", err 834 } 835 836 // ConvertRequestToIoReader converts req to XML data 837 func ConvertRequestToIoReader(req interface{}) (io.Reader, error) { 838 body, err := TransToXml(req) 839 if err == nil { 840 if isDebugLogEnabled() { 841 doLog(LEVEL_DEBUG, "Do http request with data: %s", string(body)) 842 } 843 return bytes.NewReader(body), nil 844 } 845 return nil, err 846 } 847 848 func parseBucketPolicyOutput(s reflect.Type, baseModel IBaseModel, body []byte) { 849 for i := 0; i < s.NumField(); i++ { 850 if s.Field(i).Tag == "json:\"body\"" { 851 reflect.ValueOf(baseModel).Elem().FieldByName(s.Field(i).Name).SetString(string(body)) 852 break 853 } 854 } 855 } 856 857 // ParseResponseToBaseModel gets response from OBS 858 func ParseResponseToBaseModel(resp *http.Response, baseModel IBaseModel, xmlResult bool, isObs bool) (err error) { 859 readCloser, ok := baseModel.(IReadCloser) 860 if !ok { 861 defer func() { 862 errMsg := resp.Body.Close() 863 if errMsg != nil { 864 doLog(LEVEL_WARN, "Failed to close response body") 865 } 866 }() 867 var body []byte 868 body, err = ioutil.ReadAll(resp.Body) 869 if err == nil && len(body) > 0 { 870 if xmlResult { 871 err = ParseXml(body, baseModel) 872 } else { 873 s := reflect.TypeOf(baseModel).Elem() 874 if reflect.TypeOf(baseModel).Elem().Name() == "GetBucketPolicyOutput" { 875 parseBucketPolicyOutput(s, baseModel, body) 876 } else { 877 err = parseJSON(body, baseModel) 878 } 879 } 880 if err != nil { 881 doLog(LEVEL_ERROR, "Unmarshal error: %v", err) 882 } 883 } 884 } else { 885 readCloser.setReadCloser(resp.Body) 886 } 887 888 baseModel.setStatusCode(resp.StatusCode) 889 responseHeaders := cleanHeaderPrefix(resp.Header) 890 baseModel.setResponseHeaders(responseHeaders) 891 if values, ok := responseHeaders[HEADER_REQUEST_ID]; ok { 892 baseModel.setRequestID(values[0]) 893 } 894 return 895 } 896 897 // ParseResponseToObsError gets obsError from OBS 898 func ParseResponseToObsError(resp *http.Response, isObs bool) error { 899 isJson := false 900 if contentType, ok := resp.Header[HEADER_CONTENT_TYPE_CAML]; ok { 901 jsonType, _ := mimeTypes["json"] 902 isJson = contentType[0] == jsonType 903 } 904 obsError := ObsError{} 905 respError := ParseResponseToBaseModel(resp, &obsError, !isJson, isObs) 906 if respError != nil { 907 doLog(LEVEL_WARN, "Parse response to BaseModel with error: %v", respError) 908 } 909 obsError.Status = resp.Status 910 return obsError 911 } 912 913 // convertFetchPolicyToJSON converts SetBucketFetchPolicyInput into json format 914 func convertFetchPolicyToJSON(input SetBucketFetchPolicyInput) (data string, err error) { 915 fetch := map[string]SetBucketFetchPolicyInput{"fetch": input} 916 json, err := json.Marshal(fetch) 917 if err != nil { 918 return "", err 919 } 920 data = string(json) 921 return 922 } 923 924 // convertFetchJobToJSON converts SetBucketFetchJobInput into json format 925 func convertFetchJobToJSON(input SetBucketFetchJobInput) (data string, err error) { 926 objectHeaders := make(map[string]string) 927 for key, value := range input.ObjectHeaders { 928 if value != "" { 929 _key := strings.ToLower(key) 930 if !strings.HasPrefix(key, HEADER_PREFIX_OBS) { 931 _key = HEADER_PREFIX_META_OBS + _key 932 } 933 objectHeaders[_key] = value 934 } 935 } 936 input.ObjectHeaders = objectHeaders 937 json, err := json.Marshal(input) 938 if err != nil { 939 return "", err 940 } 941 data = string(json) 942 return 943 } 944 945 func parseStringToFSStatusType(value string) (ret FSStatusType) { 946 switch value { 947 case "Enabled": 948 ret = FSStatusEnabled 949 case "Disabled": 950 ret = FSStatusDisabled 951 default: 952 ret = "" 953 } 954 return 955 } 956 957 func decodeListObjectsOutput(output *ListObjectsOutput) (err error) { 958 output.Delimiter, err = url.QueryUnescape(output.Delimiter) 959 if err != nil { 960 return 961 } 962 output.Marker, err = url.QueryUnescape(output.Marker) 963 if err != nil { 964 return 965 } 966 output.NextMarker, err = url.QueryUnescape(output.NextMarker) 967 if err != nil { 968 return 969 } 970 output.Prefix, err = url.QueryUnescape(output.Prefix) 971 if err != nil { 972 return 973 } 974 for index, value := range output.CommonPrefixes { 975 output.CommonPrefixes[index], err = url.QueryUnescape(value) 976 if err != nil { 977 return 978 } 979 } 980 for index, content := range output.Contents { 981 output.Contents[index].Key, err = url.QueryUnescape(content.Key) 982 if err != nil { 983 return 984 } 985 } 986 return 987 } 988 989 func decodeListVersionsOutput(output *ListVersionsOutput) (err error) { 990 output.Delimiter, err = url.QueryUnescape(output.Delimiter) 991 if err != nil { 992 return 993 } 994 output.KeyMarker, err = url.QueryUnescape(output.KeyMarker) 995 if err != nil { 996 return 997 } 998 output.NextKeyMarker, err = url.QueryUnescape(output.NextKeyMarker) 999 if err != nil { 1000 return 1001 } 1002 output.Prefix, err = url.QueryUnescape(output.Prefix) 1003 if err != nil { 1004 return 1005 } 1006 for index, version := range output.Versions { 1007 output.Versions[index].Key, err = url.QueryUnescape(version.Key) 1008 if err != nil { 1009 return 1010 } 1011 } 1012 for index, deleteMarker := range output.DeleteMarkers { 1013 output.DeleteMarkers[index].Key, err = url.QueryUnescape(deleteMarker.Key) 1014 if err != nil { 1015 return 1016 } 1017 } 1018 for index, value := range output.CommonPrefixes { 1019 output.CommonPrefixes[index], err = url.QueryUnescape(value) 1020 if err != nil { 1021 return 1022 } 1023 } 1024 return 1025 } 1026 1027 func decodeDeleteObjectsOutput(output *DeleteObjectsOutput) (err error) { 1028 for index, object := range output.Deleteds { 1029 output.Deleteds[index].Key, err = url.QueryUnescape(object.Key) 1030 if err != nil { 1031 return 1032 } 1033 } 1034 for index, object := range output.Errors { 1035 output.Errors[index].Key, err = url.QueryUnescape(object.Key) 1036 if err != nil { 1037 return 1038 } 1039 } 1040 return 1041 } 1042 1043 func decodeListMultipartUploadsOutput(output *ListMultipartUploadsOutput) (err error) { 1044 output.Delimiter, err = url.QueryUnescape(output.Delimiter) 1045 if err != nil { 1046 return 1047 } 1048 output.Prefix, err = url.QueryUnescape(output.Prefix) 1049 if err != nil { 1050 return 1051 } 1052 output.KeyMarker, err = url.QueryUnescape(output.KeyMarker) 1053 if err != nil { 1054 return 1055 } 1056 output.NextKeyMarker, err = url.QueryUnescape(output.NextKeyMarker) 1057 if err != nil { 1058 return 1059 } 1060 for index, value := range output.CommonPrefixes { 1061 output.CommonPrefixes[index], err = url.QueryUnescape(value) 1062 if err != nil { 1063 return 1064 } 1065 } 1066 for index, upload := range output.Uploads { 1067 output.Uploads[index].Key, err = url.QueryUnescape(upload.Key) 1068 if err != nil { 1069 return 1070 } 1071 } 1072 return 1073 } 1074 1075 func decodeListPartsOutput(output *ListPartsOutput) (err error) { 1076 output.Key, err = url.QueryUnescape(output.Key) 1077 return 1078 } 1079 1080 func decodeInitiateMultipartUploadOutput(output *InitiateMultipartUploadOutput) (err error) { 1081 output.Key, err = url.QueryUnescape(output.Key) 1082 return 1083 } 1084 1085 func decodeCompleteMultipartUploadOutput(output *CompleteMultipartUploadOutput) (err error) { 1086 output.Key, err = url.QueryUnescape(output.Key) 1087 return 1088 }