host_style_bucket.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package s3
  2. import (
  3. "fmt"
  4. "net/url"
  5. "regexp"
  6. "strings"
  7. "github.com/aws/aws-sdk-go/aws"
  8. "github.com/aws/aws-sdk-go/aws/awserr"
  9. "github.com/aws/aws-sdk-go/aws/request"
  10. )
  11. // an operationBlacklist is a list of operation names that should a
  12. // request handler should not be executed with.
  13. type operationBlacklist []string
  14. // Continue will return true of the Request's operation name is not
  15. // in the blacklist. False otherwise.
  16. func (b operationBlacklist) Continue(r *request.Request) bool {
  17. for i := 0; i < len(b); i++ {
  18. if b[i] == r.Operation.Name {
  19. return false
  20. }
  21. }
  22. return true
  23. }
  24. var accelerateOpBlacklist = operationBlacklist{
  25. opListBuckets, opCreateBucket, opDeleteBucket,
  26. }
  27. // Request handler to automatically add the bucket name to the endpoint domain
  28. // if possible. This style of bucket is valid for all bucket names which are
  29. // DNS compatible and do not contain "."
  30. func updateEndpointForS3Config(r *request.Request) {
  31. forceHostStyle := aws.BoolValue(r.Config.S3ForcePathStyle)
  32. accelerate := aws.BoolValue(r.Config.S3UseAccelerate)
  33. if accelerate && accelerateOpBlacklist.Continue(r) {
  34. if forceHostStyle {
  35. if r.Config.Logger != nil {
  36. r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.")
  37. }
  38. }
  39. updateEndpointForAccelerate(r)
  40. } else if !forceHostStyle && r.Operation.Name != opGetBucketLocation {
  41. updateEndpointForHostStyle(r)
  42. }
  43. }
  44. func updateEndpointForHostStyle(r *request.Request) {
  45. bucket, ok := bucketNameFromReqParams(r.Params)
  46. if !ok {
  47. // Ignore operation requests if the bucketname was not provided
  48. // if this is an input validation error the validation handler
  49. // will report it.
  50. return
  51. }
  52. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  53. // bucket name must be valid to put into the host
  54. return
  55. }
  56. moveBucketToHost(r.HTTPRequest.URL, bucket)
  57. }
  58. var (
  59. accelElem = []byte("s3-accelerate.dualstack.")
  60. )
  61. func updateEndpointForAccelerate(r *request.Request) {
  62. bucket, ok := bucketNameFromReqParams(r.Params)
  63. if !ok {
  64. // Ignore operation requests if the bucketname was not provided
  65. // if this is an input validation error the validation handler
  66. // will report it.
  67. return
  68. }
  69. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  70. r.Error = awserr.New("InvalidParameterException",
  71. fmt.Sprintf("bucket name %s is not compatible with S3 Accelerate", bucket),
  72. nil)
  73. return
  74. }
  75. parts := strings.Split(r.HTTPRequest.URL.Host, ".")
  76. if len(parts) < 3 {
  77. r.Error = awserr.New("InvalidParameterExecption",
  78. fmt.Sprintf("unable to update endpoint host for S3 accelerate, hostname invalid, %s",
  79. r.HTTPRequest.URL.Host), nil)
  80. return
  81. }
  82. if parts[0] == "s3" || strings.HasPrefix(parts[0], "s3-") {
  83. parts[0] = "s3-accelerate"
  84. }
  85. for i := 1; i+1 < len(parts); i++ {
  86. if parts[i] == aws.StringValue(r.Config.Region) {
  87. parts = append(parts[:i], parts[i+1:]...)
  88. break
  89. }
  90. }
  91. r.HTTPRequest.URL.Host = strings.Join(parts, ".")
  92. moveBucketToHost(r.HTTPRequest.URL, bucket)
  93. }
  94. // Attempts to retrieve the bucket name from the request input parameters.
  95. // If no bucket is found, or the field is empty "", false will be returned.
  96. func bucketNameFromReqParams(params interface{}) (string, bool) {
  97. if iface, ok := params.(bucketGetter); ok {
  98. b := iface.getBucket()
  99. return b, len(b) > 0
  100. }
  101. return "", false
  102. }
  103. // hostCompatibleBucketName returns true if the request should
  104. // put the bucket in the host. This is false if S3ForcePathStyle is
  105. // explicitly set or if the bucket is not DNS compatible.
  106. func hostCompatibleBucketName(u *url.URL, bucket string) bool {
  107. // Bucket might be DNS compatible but dots in the hostname will fail
  108. // certificate validation, so do not use host-style.
  109. if u.Scheme == "https" && strings.Contains(bucket, ".") {
  110. return false
  111. }
  112. // if the bucket is DNS compatible
  113. return dnsCompatibleBucketName(bucket)
  114. }
  115. var reDomain = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
  116. var reIPAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`)
  117. // dnsCompatibleBucketName returns true if the bucket name is DNS compatible.
  118. // Buckets created outside of the classic region MUST be DNS compatible.
  119. func dnsCompatibleBucketName(bucket string) bool {
  120. return reDomain.MatchString(bucket) &&
  121. !reIPAddress.MatchString(bucket) &&
  122. !strings.Contains(bucket, "..")
  123. }
  124. // moveBucketToHost moves the bucket name from the URI path to URL host.
  125. func moveBucketToHost(u *url.URL, bucket string) {
  126. u.Host = bucket + "." + u.Host
  127. u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
  128. if u.Path == "" {
  129. u.Path = "/"
  130. }
  131. }