host_style_bucket.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package s3
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net/url"
  6. "regexp"
  7. "strings"
  8. "github.com/aws/aws-sdk-go/aws"
  9. "github.com/aws/aws-sdk-go/aws/awserr"
  10. "github.com/aws/aws-sdk-go/aws/awsutil"
  11. "github.com/aws/aws-sdk-go/aws/request"
  12. )
  13. // an operationBlacklist is a list of operation names that should a
  14. // request handler should not be executed with.
  15. type operationBlacklist []string
  16. // Continue will return true of the Request's operation name is not
  17. // in the blacklist. False otherwise.
  18. func (b operationBlacklist) Continue(r *request.Request) bool {
  19. for i := 0; i < len(b); i++ {
  20. if b[i] == r.Operation.Name {
  21. return false
  22. }
  23. }
  24. return true
  25. }
  26. var accelerateOpBlacklist = operationBlacklist{
  27. opListBuckets, opCreateBucket, opDeleteBucket,
  28. }
  29. // Request handler to automatically add the bucket name to the endpoint domain
  30. // if possible. This style of bucket is valid for all bucket names which are
  31. // DNS compatible and do not contain "."
  32. func updateEndpointForS3Config(r *request.Request) {
  33. forceHostStyle := aws.BoolValue(r.Config.S3ForcePathStyle)
  34. accelerate := aws.BoolValue(r.Config.S3UseAccelerate)
  35. if accelerate && accelerateOpBlacklist.Continue(r) {
  36. if forceHostStyle {
  37. if r.Config.Logger != nil {
  38. r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.")
  39. }
  40. }
  41. updateEndpointForAccelerate(r)
  42. } else if !forceHostStyle && r.Operation.Name != opGetBucketLocation {
  43. updateEndpointForHostStyle(r)
  44. }
  45. }
  46. func updateEndpointForHostStyle(r *request.Request) {
  47. bucket, ok := bucketNameFromReqParams(r.Params)
  48. if !ok {
  49. // Ignore operation requests if the bucketname was not provided
  50. // if this is an input validation error the validation handler
  51. // will report it.
  52. return
  53. }
  54. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  55. // bucket name must be valid to put into the host
  56. return
  57. }
  58. moveBucketToHost(r.HTTPRequest.URL, bucket)
  59. }
  60. var (
  61. accelElem = []byte("s3-accelerate.dualstack.")
  62. )
  63. func updateEndpointForAccelerate(r *request.Request) {
  64. bucket, ok := bucketNameFromReqParams(r.Params)
  65. if !ok {
  66. // Ignore operation requests if the bucketname was not provided
  67. // if this is an input validation error the validation handler
  68. // will report it.
  69. return
  70. }
  71. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  72. r.Error = awserr.New("InvalidParameterException",
  73. fmt.Sprintf("bucket name %s is not compatibile with S3 Accelerate", bucket),
  74. nil)
  75. return
  76. }
  77. // Change endpoint from s3(-[a-z0-1-])?.amazonaws.com to s3-accelerate.amazonaws.com
  78. r.HTTPRequest.URL.Host = replaceHostRegion(r.HTTPRequest.URL.Host, "accelerate")
  79. if aws.BoolValue(r.Config.UseDualStack) {
  80. host := []byte(r.HTTPRequest.URL.Host)
  81. // Strip region from hostname
  82. if idx := bytes.Index(host, accelElem); idx >= 0 {
  83. start := idx + len(accelElem)
  84. if end := bytes.IndexByte(host[start:], '.'); end >= 0 {
  85. end += start + 1
  86. copy(host[start:], host[end:])
  87. host = host[:len(host)-(end-start)]
  88. r.HTTPRequest.URL.Host = string(host)
  89. }
  90. }
  91. }
  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. b, _ := awsutil.ValuesAtPath(params, "Bucket")
  98. if len(b) == 0 {
  99. return "", false
  100. }
  101. if bucket, ok := b[0].(*string); ok {
  102. if bucketStr := aws.StringValue(bucket); bucketStr != "" {
  103. return bucketStr, true
  104. }
  105. }
  106. return "", false
  107. }
  108. // hostCompatibleBucketName returns true if the request should
  109. // put the bucket in the host. This is false if S3ForcePathStyle is
  110. // explicitly set or if the bucket is not DNS compatible.
  111. func hostCompatibleBucketName(u *url.URL, bucket string) bool {
  112. // Bucket might be DNS compatible but dots in the hostname will fail
  113. // certificate validation, so do not use host-style.
  114. if u.Scheme == "https" && strings.Contains(bucket, ".") {
  115. return false
  116. }
  117. // if the bucket is DNS compatible
  118. return dnsCompatibleBucketName(bucket)
  119. }
  120. var reDomain = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
  121. var reIPAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`)
  122. // dnsCompatibleBucketName returns true if the bucket name is DNS compatible.
  123. // Buckets created outside of the classic region MUST be DNS compatible.
  124. func dnsCompatibleBucketName(bucket string) bool {
  125. return reDomain.MatchString(bucket) &&
  126. !reIPAddress.MatchString(bucket) &&
  127. !strings.Contains(bucket, "..")
  128. }
  129. // moveBucketToHost moves the bucket name from the URI path to URL host.
  130. func moveBucketToHost(u *url.URL, bucket string) {
  131. u.Host = bucket + "." + u.Host
  132. u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
  133. if u.Path == "" {
  134. u.Path = "/"
  135. }
  136. }
  137. const s3HostPrefix = "s3"
  138. // replaceHostRegion replaces the S3 region string in the host with the
  139. // value provided. If v is empty the host prefix returned will be s3.
  140. func replaceHostRegion(host, v string) string {
  141. if !strings.HasPrefix(host, s3HostPrefix) {
  142. return host
  143. }
  144. suffix := host[len(s3HostPrefix):]
  145. for i := len(s3HostPrefix); i < len(host); i++ {
  146. if host[i] == '.' {
  147. // Trim until '.' leave the it in place.
  148. suffix = host[i:]
  149. break
  150. }
  151. }
  152. if len(v) == 0 {
  153. return fmt.Sprintf("s3%s", suffix)
  154. }
  155. return fmt.Sprintf("s3-%s%s", v, suffix)
  156. }