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

277 lines
7.1 KiB

  1. /*
  2. * Minio Cloud Storage, (C) 2015 Minio, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cmd
  17. import (
  18. "encoding/base64"
  19. "encoding/xml"
  20. "errors"
  21. "fmt"
  22. "io"
  23. "net/http"
  24. "net/url"
  25. "os"
  26. "strings"
  27. "encoding/json"
  28. humanize "github.com/dustin/go-humanize"
  29. "github.com/pkg/profile"
  30. )
  31. // make a copy of http.Header
  32. func cloneHeader(h http.Header) http.Header {
  33. h2 := make(http.Header, len(h))
  34. for k, vv := range h {
  35. vv2 := make([]string, len(vv))
  36. copy(vv2, vv)
  37. h2[k] = vv2
  38. }
  39. return h2
  40. }
  41. // checkDuplicates - function to validate if there are duplicates in a slice of strings.
  42. func checkDuplicateStrings(list []string) error {
  43. // Empty lists are not allowed.
  44. if len(list) == 0 {
  45. return errInvalidArgument
  46. }
  47. // Empty keys are not allowed.
  48. for _, key := range list {
  49. if key == "" {
  50. return errInvalidArgument
  51. }
  52. }
  53. listMaps := make(map[string]int)
  54. // Navigate through each configs and count the entries.
  55. for _, key := range list {
  56. listMaps[key]++
  57. }
  58. // Validate if there are any duplicate counts.
  59. for key, count := range listMaps {
  60. if count != 1 {
  61. return fmt.Errorf("Duplicate key: \"%s\" found of count: \"%d\"", key, count)
  62. }
  63. }
  64. // No duplicates.
  65. return nil
  66. }
  67. // splitStr splits a string into n parts, empty strings are added
  68. // if we are not able to reach n elements
  69. func splitStr(path, sep string, n int) []string {
  70. splits := strings.SplitN(path, sep, n)
  71. // Add empty strings if we found elements less than nr
  72. for i := n - len(splits); i > 0; i-- {
  73. splits = append(splits, "")
  74. }
  75. return splits
  76. }
  77. // Convert url path into bucket and object name.
  78. func urlPath2BucketObjectName(u *url.URL) (bucketName, objectName string) {
  79. if u == nil {
  80. // Empty url, return bucket and object names.
  81. return
  82. }
  83. // Trim any preceding slash separator.
  84. urlPath := strings.TrimPrefix(u.Path, slashSeparator)
  85. // Split urlpath using slash separator into a given number of
  86. // expected tokens.
  87. tokens := splitStr(urlPath, slashSeparator, 2)
  88. // Extract bucket and objects.
  89. bucketName, objectName = tokens[0], tokens[1]
  90. // Success.
  91. return bucketName, objectName
  92. }
  93. // URI scheme constants.
  94. const (
  95. httpScheme = "http"
  96. httpsScheme = "https"
  97. )
  98. var portMap = map[string]string{
  99. httpScheme: "80",
  100. httpsScheme: "443",
  101. }
  102. // Given a string of the form "host", "host:port", or "[ipv6::address]:port",
  103. // return true if the string includes a port.
  104. func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
  105. // canonicalAddr returns url.Host but always with a ":port" suffix
  106. func canonicalAddr(u *url.URL) string {
  107. addr := u.Host
  108. if !hasPort(addr) {
  109. return addr + ":" + portMap[u.Scheme]
  110. }
  111. return addr
  112. }
  113. // checkDuplicates - function to validate if there are duplicates in a slice of endPoints.
  114. func checkDuplicateEndpoints(endpoints []*url.URL) error {
  115. var strs []string
  116. for _, ep := range endpoints {
  117. strs = append(strs, ep.String())
  118. }
  119. return checkDuplicateStrings(strs)
  120. }
  121. // Find local node through the command line arguments. Returns in `host:port` format.
  122. func getLocalAddress(srvCmdConfig serverCmdConfig) string {
  123. if !globalIsDistXL {
  124. return srvCmdConfig.serverAddr
  125. }
  126. for _, ep := range srvCmdConfig.endpoints {
  127. // Validates if remote endpoint is local.
  128. if isLocalStorage(ep) {
  129. return ep.Host
  130. }
  131. }
  132. return ""
  133. }
  134. // xmlDecoder provide decoded value in xml.
  135. func xmlDecoder(body io.Reader, v interface{}, size int64) error {
  136. var lbody io.Reader
  137. if size > 0 {
  138. lbody = io.LimitReader(body, size)
  139. } else {
  140. lbody = body
  141. }
  142. d := xml.NewDecoder(lbody)
  143. return d.Decode(v)
  144. }
  145. // checkValidMD5 - verify if valid md5, returns md5 in bytes.
  146. func checkValidMD5(md5 string) ([]byte, error) {
  147. return base64.StdEncoding.DecodeString(strings.TrimSpace(md5))
  148. }
  149. /// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
  150. const (
  151. // Maximum object size per PUT request is 16GiB.
  152. // This is a divergence from S3 limit on purpose to support
  153. // use cases where users are going to upload large files
  154. // using 'curl' and presigned URL.
  155. globalMaxObjectSize = 16 * humanize.GiByte
  156. // Minimum Part size for multipart upload is 5MiB
  157. globalMinPartSize = 5 * humanize.MiByte
  158. // Maximum Part size for multipart upload is 5GiB
  159. globalMaxPartSize = 5 * humanize.GiByte
  160. // Maximum Part ID for multipart upload is 10000
  161. // (Acceptable values range from 1 to 10000 inclusive)
  162. globalMaxPartID = 10000
  163. )
  164. // isMaxObjectSize - verify if max object size
  165. func isMaxObjectSize(size int64) bool {
  166. return size > globalMaxObjectSize
  167. }
  168. // // Check if part size is more than maximum allowed size.
  169. func isMaxAllowedPartSize(size int64) bool {
  170. return size > globalMaxPartSize
  171. }
  172. // Check if part size is more than or equal to minimum allowed size.
  173. func isMinAllowedPartSize(size int64) bool {
  174. return size >= globalMinPartSize
  175. }
  176. // isMaxPartNumber - Check if part ID is greater than the maximum allowed ID.
  177. func isMaxPartID(partID int) bool {
  178. return partID > globalMaxPartID
  179. }
  180. func contains(stringList []string, element string) bool {
  181. for _, e := range stringList {
  182. if e == element {
  183. return true
  184. }
  185. }
  186. return false
  187. }
  188. // Starts a profiler returns nil if profiler is not enabled, caller needs to handle this.
  189. func startProfiler(profiler string) interface {
  190. Stop()
  191. } {
  192. // Enable profiler if ``_MINIO_PROFILER`` is set. Supported options are [cpu, mem, block].
  193. switch profiler {
  194. case "cpu":
  195. return profile.Start(profile.CPUProfile, profile.NoShutdownHook)
  196. case "mem":
  197. return profile.Start(profile.MemProfile, profile.NoShutdownHook)
  198. case "block":
  199. return profile.Start(profile.BlockProfile, profile.NoShutdownHook)
  200. default:
  201. return nil
  202. }
  203. }
  204. // Global profiler to be used by service go-routine.
  205. var globalProfiler interface {
  206. Stop()
  207. }
  208. // dump the request into a string in JSON format.
  209. func dumpRequest(r *http.Request) string {
  210. header := cloneHeader(r.Header)
  211. header.Set("Host", r.Host)
  212. req := struct {
  213. Method string `json:"method"`
  214. Path string `json:"path"`
  215. Query string `json:"query"`
  216. Header http.Header `json:"header"`
  217. }{r.Method, r.URL.Path, r.URL.RawQuery, header}
  218. jsonBytes, err := json.Marshal(req)
  219. if err != nil {
  220. return "<error dumping request>"
  221. }
  222. return string(jsonBytes)
  223. }
  224. // isFile - returns whether given path is a file or not.
  225. func isFile(path string) bool {
  226. if fi, err := os.Stat(path); err == nil {
  227. return fi.Mode().IsRegular()
  228. }
  229. return false
  230. }
  231. // checkURL - checks if passed address correspond
  232. func checkURL(address string) (*url.URL, error) {
  233. if address == "" {
  234. return nil, errors.New("Address cannot be empty")
  235. }
  236. u, err := url.Parse(address)
  237. if err != nil {
  238. return nil, fmt.Errorf("`%s` invalid: %s", address, err.Error())
  239. }
  240. return u, nil
  241. }