utils.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. /*
  2. * MinIO Go Library for Amazon S3 Compatible Cloud Storage
  3. * Copyright 2015-2017 MinIO, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package s3cli
  18. import (
  19. "crypto/md5"
  20. "crypto/sha256"
  21. "encoding/base64"
  22. "encoding/hex"
  23. "encoding/xml"
  24. "io"
  25. "io/ioutil"
  26. "net"
  27. "net/http"
  28. "net/url"
  29. "regexp"
  30. "strings"
  31. "time"
  32. "github.com/minio/minio-go/v6/pkg/s3utils"
  33. )
  34. // xmlDecoder provide decoded value in xml.
  35. func xmlDecoder(body io.Reader, v interface{}) error {
  36. d := xml.NewDecoder(body)
  37. return d.Decode(v)
  38. }
  39. // sum256 calculate sha256sum for an input byte array, returns hex encoded.
  40. func sum256Hex(data []byte) string {
  41. hash := sha256.New()
  42. hash.Write(data)
  43. return hex.EncodeToString(hash.Sum(nil))
  44. }
  45. // sumMD5Base64 calculate md5sum for an input byte array, returns base64 encoded.
  46. func sumMD5Base64(data []byte) string {
  47. hash := md5.New()
  48. hash.Write(data)
  49. return base64.StdEncoding.EncodeToString(hash.Sum(nil))
  50. }
  51. // getEndpointURL - construct a new endpoint.
  52. func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
  53. if strings.Contains(endpoint, ":") {
  54. host, _, err := net.SplitHostPort(endpoint)
  55. if err != nil {
  56. return nil, err
  57. }
  58. if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
  59. msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
  60. return nil, ErrInvalidArgument(msg)
  61. }
  62. } else {
  63. if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
  64. msg := "Endpoint: " + endpoint + " does not follow ip address or domain name standards."
  65. return nil, ErrInvalidArgument(msg)
  66. }
  67. }
  68. // If secure is false, use 'http' scheme.
  69. scheme := "https"
  70. if !secure {
  71. scheme = "http"
  72. }
  73. // Construct a secured endpoint URL.
  74. endpointURLStr := scheme + "://" + endpoint
  75. endpointURL, err := url.Parse(endpointURLStr)
  76. if err != nil {
  77. return nil, err
  78. }
  79. // Validate incoming endpoint URL.
  80. if err := isValidEndpointURL(*endpointURL); err != nil {
  81. return nil, err
  82. }
  83. return endpointURL, nil
  84. }
  85. // closeResponse close non nil response with any response Body.
  86. // convenient wrapper to drain any remaining data on response body.
  87. //
  88. // Subsequently this allows golang http RoundTripper
  89. // to re-use the same connection for future requests.
  90. func closeResponse(resp *http.Response) {
  91. // Callers should close resp.Body when done reading from it.
  92. // If resp.Body is not closed, the Client's underlying RoundTripper
  93. // (typically Transport) may not be able to re-use a persistent TCP
  94. // connection to the server for a subsequent "keep-alive" request.
  95. if resp != nil && resp.Body != nil {
  96. // Drain any remaining Body and then close the connection.
  97. // Without this closing connection would disallow re-using
  98. // the same connection for future uses.
  99. // - http://stackoverflow.com/a/17961593/4465767
  100. io.Copy(ioutil.Discard, resp.Body)
  101. resp.Body.Close()
  102. }
  103. }
  104. var (
  105. // Hex encoded string of nil sha256sum bytes.
  106. emptySHA256Hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  107. // Sentinel URL is the default url value which is invalid.
  108. sentinelURL = url.URL{}
  109. )
  110. // Verify if input endpoint URL is valid.
  111. func isValidEndpointURL(endpointURL url.URL) error {
  112. if endpointURL == sentinelURL {
  113. return ErrInvalidArgument("Endpoint url cannot be empty.")
  114. }
  115. if endpointURL.Path != "/" && endpointURL.Path != "" {
  116. return ErrInvalidArgument("Endpoint url cannot have fully qualified paths.")
  117. }
  118. if strings.Contains(endpointURL.Host, ".s3.amazonaws.com") {
  119. if !s3utils.IsAmazonEndpoint(endpointURL) {
  120. return ErrInvalidArgument("Amazon S3 endpoint should be 's3.amazonaws.com'.")
  121. }
  122. }
  123. if strings.Contains(endpointURL.Host, ".googleapis.com") {
  124. if !s3utils.IsGoogleEndpoint(endpointURL) {
  125. return ErrInvalidArgument("Google Cloud Storage endpoint should be 'storage.googleapis.com'.")
  126. }
  127. }
  128. return nil
  129. }
  130. // Verify if input expires value is valid.
  131. func isValidExpiry(expires time.Duration) error {
  132. expireSeconds := int64(expires / time.Second)
  133. if expireSeconds < 1 {
  134. return ErrInvalidArgument("Expires cannot be lesser than 1 second.")
  135. }
  136. if expireSeconds > 604800 {
  137. return ErrInvalidArgument("Expires cannot be greater than 7 days.")
  138. }
  139. return nil
  140. }
  141. // make a copy of http.Header
  142. func cloneHeader(h http.Header) http.Header {
  143. h2 := make(http.Header, len(h))
  144. for k, vv := range h {
  145. vv2 := make([]string, len(vv))
  146. copy(vv2, vv)
  147. h2[k] = vv2
  148. }
  149. return h2
  150. }
  151. // Filter relevant response headers from
  152. // the HEAD, GET http response. The function takes
  153. // a list of headers which are filtered out and
  154. // returned as a new http header.
  155. func filterHeader(header http.Header, filterKeys []string) (filteredHeader http.Header) {
  156. filteredHeader = cloneHeader(header)
  157. for _, key := range filterKeys {
  158. filteredHeader.Del(key)
  159. }
  160. return filteredHeader
  161. }
  162. // regCred matches credential string in HTTP header
  163. var regCred = regexp.MustCompile("Credential=([A-Z0-9]+)/")
  164. // regCred matches signature string in HTTP header
  165. var regSign = regexp.MustCompile("Signature=([[0-9a-f]+)")
  166. // Redact out signature value from authorization string.
  167. func redactSignature(origAuth string) string {
  168. if !strings.HasPrefix(origAuth, signV4Algorithm) {
  169. // Set a temporary redacted auth
  170. return "AWS **REDACTED**:**REDACTED**"
  171. }
  172. /// Signature V4 authorization header.
  173. // Strip out accessKeyID from:
  174. // Credential=<access-key-id>/<date>/<aws-region>/<aws-service>/aws4_request
  175. newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/")
  176. // Strip out 256-bit signature from: Signature=<256-bit signature>
  177. return regSign.ReplaceAllString(newAuth, "Signature=**REDACTED**")
  178. }
  179. // Get default location returns the location based on the input
  180. // URL `u`, if region override is provided then all location
  181. // defaults to regionOverride.
  182. //
  183. // If no other cases match then the location is set to `us-east-1`
  184. // as a last resort.
  185. func getDefaultLocation(u url.URL, regionOverride string) (location string) {
  186. if regionOverride != "" {
  187. return regionOverride
  188. }
  189. region := s3utils.GetRegionFromURL(u)
  190. if region == "" {
  191. region = "us-east-1"
  192. }
  193. return region
  194. }
  195. var supportedHeaders = []string{
  196. "content-type",
  197. "cache-control",
  198. "content-encoding",
  199. "content-disposition",
  200. "content-language",
  201. "x-amz-website-redirect-location",
  202. "expires",
  203. // Add more supported headers here.
  204. }
  205. // isStorageClassHeader returns true if the header is a supported storage class header
  206. func isStorageClassHeader(headerKey string) bool {
  207. return strings.EqualFold(amzStorageClass, headerKey)
  208. }
  209. // isStandardHeader returns true if header is a supported header and not a custom header
  210. func isStandardHeader(headerKey string) bool {
  211. key := strings.ToLower(headerKey)
  212. for _, header := range supportedHeaders {
  213. if strings.ToLower(header) == key {
  214. return true
  215. }
  216. }
  217. return false
  218. }
  219. // sseHeaders is list of server side encryption headers
  220. var sseHeaders = []string{
  221. "x-amz-server-side-encryption",
  222. "x-amz-server-side-encryption-aws-kms-key-id",
  223. "x-amz-server-side-encryption-context",
  224. "x-amz-server-side-encryption-customer-algorithm",
  225. "x-amz-server-side-encryption-customer-key",
  226. "x-amz-server-side-encryption-customer-key-MD5",
  227. }
  228. // isSSEHeader returns true if header is a server side encryption header.
  229. func isSSEHeader(headerKey string) bool {
  230. key := strings.ToLower(headerKey)
  231. for _, h := range sseHeaders {
  232. if strings.ToLower(h) == key {
  233. return true
  234. }
  235. }
  236. return false
  237. }
  238. // isAmzHeader returns true if header is a x-amz-meta-* or x-amz-acl header.
  239. func isAmzHeader(headerKey string) bool {
  240. key := strings.ToLower(headerKey)
  241. return strings.HasPrefix(key, "x-amz-meta-") || strings.HasPrefix(key, "x-amz-grant-") || key == "x-amz-acl" || isSSEHeader(headerKey)
  242. }