You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

util.go 5.7 kB

5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package minio_ext
  2. import (
  3. "crypto/sha256"
  4. "encoding/hex"
  5. "encoding/xml"
  6. "io"
  7. "io/ioutil"
  8. "net"
  9. "net/http"
  10. "net/url"
  11. "regexp"
  12. "strings"
  13. "github.com/minio/minio-go/v6/pkg/s3utils"
  14. )
  15. // regCred matches credential string in HTTP header
  16. var regCred = regexp.MustCompile("Credential=([A-Z0-9]+)/")
  17. // regCred matches signature string in HTTP header
  18. var regSign = regexp.MustCompile("Signature=([[0-9a-f]+)")
  19. // xmlDecoder provide decoded value in xml.
  20. func xmlDecoder(body io.Reader, v interface{}) error {
  21. d := xml.NewDecoder(body)
  22. return d.Decode(v)
  23. }
  24. // Redact out signature value from authorization string.
  25. func redactSignature(origAuth string) string {
  26. if !strings.HasPrefix(origAuth, signV4Algorithm) {
  27. // Set a temporary redacted auth
  28. return "AWS **REDACTED**:**REDACTED**"
  29. }
  30. /// Signature V4 authorization header.
  31. // Strip out accessKeyID from:
  32. // Credential=<access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request
  33. newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/")
  34. // Strip out 256-bit signature from: Signature=<256-bit signature>
  35. return regSign.ReplaceAllString(newAuth, "Signature=**REDACTED**")
  36. }
  37. // closeResponse close non nil response with any response Body.
  38. // convenient wrapper to drain any remaining data on response body.
  39. //
  40. // Subsequently this allows golang http RoundTripper
  41. // to re-use the same connection for future requests.
  42. func closeResponse(resp *http.Response) {
  43. // Callers should close resp.Body when done reading from it.
  44. // If resp.Body is not closed, the Client's underlying RoundTripper
  45. // (typically Transport) may not be able to re-use a persistent TCP
  46. // connection to the server for a subsequent "keep-alive" request.
  47. if resp != nil && resp.Body != nil {
  48. // Drain any remaining Body and then close the connection.
  49. // Without this closing connection would disallow re-using
  50. // the same connection for future uses.
  51. // - http://stackoverflow.com/a/17961593/4465767
  52. io.Copy(ioutil.Discard, resp.Body)
  53. resp.Body.Close()
  54. }
  55. }
  56. // Verify if input endpoint URL is valid.
  57. func isValidEndpointURL(endpointURL url.URL) error {
  58. if endpointURL == sentinelURL {
  59. return ErrInvalidArgument("Endpoint url cannot be empty.")
  60. }
  61. if endpointURL.Path != "/" && endpointURL.Path != "" {
  62. return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.")
  63. }
  64. if strings.Contains(endpointURL.Host, ".s3.amazonaws.com") {
  65. if !s3utils.IsAmazonEndpoint(endpointURL) {
  66. return ErrInvalidArgument("Amazon S3 endpoint should be 's3.amazonaws.com'.")
  67. }
  68. }
  69. if strings.Contains(endpointURL.Host, ".googleapis.com") {
  70. if !s3utils.IsGoogleEndpoint(endpointURL) {
  71. return ErrInvalidArgument("Google Cloud Storage endpoint should be 'storage.googleapis.com'.")
  72. }
  73. }
  74. return nil
  75. }
  76. // getEndpointURL - construct a new endpoint.
  77. func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
  78. if strings.Contains(endpoint, ":") {
  79. host, _, err := net.SplitHostPort(endpoint)
  80. if err != nil {
  81. return nil, err
  82. }
  83. if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
  84. msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
  85. return nil, ErrInvalidArgument(msg)
  86. }
  87. } else {
  88. if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
  89. msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
  90. return nil, ErrInvalidArgument(msg)
  91. }
  92. }
  93. // If secure is false, use 'http' scheme.
  94. scheme := "https"
  95. if !secure {
  96. scheme = "http"
  97. }
  98. // Construct a secured endpoint URL.
  99. endpointURLStr := scheme + "://" + endpoint
  100. endpointURL, err := url.Parse(endpointURLStr)
  101. if err != nil {
  102. return nil, err
  103. }
  104. // Validate incoming endpoint URL.
  105. if err := isValidEndpointURL(*endpointURL); err != nil {
  106. return nil, err
  107. }
  108. return endpointURL, nil
  109. }
  110. var supportedHeaders = []string{
  111. "content-type",
  112. "cache-control",
  113. "content-encoding",
  114. "content-disposition",
  115. "content-language",
  116. "x-amz-website-redirect-location",
  117. "expires",
  118. // Add more supported headers here.
  119. }
  120. // isStorageClassHeader returns true if the header is a supported storage class header
  121. func isStorageClassHeader(headerKey string) bool {
  122. return strings.EqualFold(amzStorageClass, headerKey)
  123. }
  124. // isStandardHeader returns true if header is a supported header and not a custom header
  125. func isStandardHeader(headerKey string) bool {
  126. key := strings.ToLower(headerKey)
  127. for _, header := range supportedHeaders {
  128. if strings.ToLower(header) == key {
  129. return true
  130. }
  131. }
  132. return false
  133. }
  134. // sseHeaders is list of server side encryption headers
  135. var sseHeaders = []string{
  136. "x-amz-server-side-encryption",
  137. "x-amz-server-side-encryption-aws-kms-key-id",
  138. "x-amz-server-side-encryption-context",
  139. "x-amz-server-side-encryption-customer-algorithm",
  140. "x-amz-server-side-encryption-customer-key",
  141. "x-amz-server-side-encryption-customer-key-MD5",
  142. }
  143. // isSSEHeader returns true if header is a server side encryption header.
  144. func isSSEHeader(headerKey string) bool {
  145. key := strings.ToLower(headerKey)
  146. for _, h := range sseHeaders {
  147. if strings.ToLower(h) == key {
  148. return true
  149. }
  150. }
  151. return false
  152. }
  153. // isAmzHeader returns true if header is a x-amz-meta-* or x-amz-acl header.
  154. func isAmzHeader(headerKey string) bool {
  155. key := strings.ToLower(headerKey)
  156. return strings.HasPrefix(key, "x-amz-meta-") || strings.HasPrefix(key, "x-amz-grant-") || key == "x-amz-acl" || isSSEHeader(headerKey)
  157. }
  158. // sum256 calculate sha256sum for an input byte array, returns hex encoded.
  159. func sum256Hex(data []byte) string {
  160. hash := sha256.New()
  161. hash.Write(data)
  162. return hex.EncodeToString(hash.Sum(nil))
  163. }