aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go')
-rw-r--r--vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go411
1 files changed, 411 insertions, 0 deletions
diff --git a/vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go b/vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go
new file mode 100644
index 0000000..056e78a
--- /dev/null
+++ b/vendor/github.com/minio/minio-go/v7/pkg/s3utils/utils.go
@@ -0,0 +1,411 @@
1/*
2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
3 * Copyright 2015-2020 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
18package s3utils
19
20import (
21 "bytes"
22 "encoding/hex"
23 "errors"
24 "net"
25 "net/url"
26 "regexp"
27 "sort"
28 "strings"
29 "unicode/utf8"
30)
31
32// Sentinel URL is the default url value which is invalid.
33var sentinelURL = url.URL{}
34
35// IsValidDomain validates if input string is a valid domain name.
36func IsValidDomain(host string) bool {
37 // See RFC 1035, RFC 3696.
38 host = strings.TrimSpace(host)
39 if len(host) == 0 || len(host) > 255 {
40 return false
41 }
42 // host cannot start or end with "-"
43 if host[len(host)-1:] == "-" || host[:1] == "-" {
44 return false
45 }
46 // host cannot start or end with "_"
47 if host[len(host)-1:] == "_" || host[:1] == "_" {
48 return false
49 }
50 // host cannot start with a "."
51 if host[:1] == "." {
52 return false
53 }
54 // All non alphanumeric characters are invalid.
55 if strings.ContainsAny(host, "`~!@#$%^&*()+={}[]|\\\"';:><?/") {
56 return false
57 }
58 // No need to regexp match, since the list is non-exhaustive.
59 // We let it valid and fail later.
60 return true
61}
62
63// IsValidIP parses input string for ip address validity.
64func IsValidIP(ip string) bool {
65 return net.ParseIP(ip) != nil
66}
67
68// IsVirtualHostSupported - verifies if bucketName can be part of
69// virtual host. Currently only Amazon S3 and Google Cloud Storage
70// would support this.
71func IsVirtualHostSupported(endpointURL url.URL, bucketName string) bool {
72 if endpointURL == sentinelURL {
73 return false
74 }
75 // bucketName can be valid but '.' in the hostname will fail SSL
76 // certificate validation. So do not use host-style for such buckets.
77 if endpointURL.Scheme == "https" && strings.Contains(bucketName, ".") {
78 return false
79 }
80 // Return true for all other cases
81 return IsAmazonEndpoint(endpointURL) || IsGoogleEndpoint(endpointURL) || IsAliyunOSSEndpoint(endpointURL)
82}
83
84// Refer for region styles - https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
85
86// amazonS3HostHyphen - regular expression used to determine if an arg is s3 host in hyphenated style.
87var amazonS3HostHyphen = regexp.MustCompile(`^s3-(.*?).amazonaws.com$`)
88
89// amazonS3HostDualStack - regular expression used to determine if an arg is s3 host dualstack.
90var amazonS3HostDualStack = regexp.MustCompile(`^s3.dualstack.(.*?).amazonaws.com$`)
91
92// amazonS3HostFIPS - regular expression used to determine if an arg is s3 FIPS host.
93var amazonS3HostFIPS = regexp.MustCompile(`^s3-fips.(.*?).amazonaws.com$`)
94
95// amazonS3HostFIPSDualStack - regular expression used to determine if an arg is s3 FIPS host dualstack.
96var amazonS3HostFIPSDualStack = regexp.MustCompile(`^s3-fips.dualstack.(.*?).amazonaws.com$`)
97
98// amazonS3HostDot - regular expression used to determine if an arg is s3 host in . style.
99var amazonS3HostDot = regexp.MustCompile(`^s3.(.*?).amazonaws.com$`)
100
101// amazonS3ChinaHost - regular expression used to determine if the arg is s3 china host.
102var amazonS3ChinaHost = regexp.MustCompile(`^s3.(cn.*?).amazonaws.com.cn$`)
103
104// amazonS3ChinaHostDualStack - regular expression used to determine if the arg is s3 china host dualstack.
105var amazonS3ChinaHostDualStack = regexp.MustCompile(`^s3.dualstack.(cn.*?).amazonaws.com.cn$`)
106
107// Regular expression used to determine if the arg is elb host.
108var elbAmazonRegex = regexp.MustCompile(`elb(.*?).amazonaws.com$`)
109
110// Regular expression used to determine if the arg is elb host in china.
111var elbAmazonCnRegex = regexp.MustCompile(`elb(.*?).amazonaws.com.cn$`)
112
113// amazonS3HostPrivateLink - regular expression used to determine if an arg is s3 host in AWS PrivateLink interface endpoints style
114var amazonS3HostPrivateLink = regexp.MustCompile(`^(?:bucket|accesspoint).vpce-.*?.s3.(.*?).vpce.amazonaws.com$`)
115
116// GetRegionFromURL - returns a region from url host.
117func GetRegionFromURL(endpointURL url.URL) string {
118 if endpointURL == sentinelURL {
119 return ""
120 }
121 if endpointURL.Host == "s3-external-1.amazonaws.com" {
122 return ""
123 }
124
125 // if elb's are used we cannot calculate which region it may be, just return empty.
126 if elbAmazonRegex.MatchString(endpointURL.Host) || elbAmazonCnRegex.MatchString(endpointURL.Host) {
127 return ""
128 }
129
130 // We check for FIPS dualstack matching first to avoid the non-greedy
131 // regex for FIPS non-dualstack matching a dualstack URL
132 parts := amazonS3HostFIPSDualStack.FindStringSubmatch(endpointURL.Host)
133 if len(parts) > 1 {
134 return parts[1]
135 }
136
137 parts = amazonS3HostFIPS.FindStringSubmatch(endpointURL.Host)
138 if len(parts) > 1 {
139 return parts[1]
140 }
141
142 parts = amazonS3HostDualStack.FindStringSubmatch(endpointURL.Host)
143 if len(parts) > 1 {
144 return parts[1]
145 }
146
147 parts = amazonS3HostHyphen.FindStringSubmatch(endpointURL.Host)
148 if len(parts) > 1 {
149 return parts[1]
150 }
151
152 parts = amazonS3ChinaHost.FindStringSubmatch(endpointURL.Host)
153 if len(parts) > 1 {
154 return parts[1]
155 }
156
157 parts = amazonS3ChinaHostDualStack.FindStringSubmatch(endpointURL.Host)
158 if len(parts) > 1 {
159 return parts[1]
160 }
161
162 parts = amazonS3HostDot.FindStringSubmatch(endpointURL.Host)
163 if len(parts) > 1 {
164 return parts[1]
165 }
166
167 parts = amazonS3HostPrivateLink.FindStringSubmatch(endpointURL.Host)
168 if len(parts) > 1 {
169 return parts[1]
170 }
171
172 return ""
173}
174
175// IsAliyunOSSEndpoint - Match if it is exactly Aliyun OSS endpoint.
176func IsAliyunOSSEndpoint(endpointURL url.URL) bool {
177 return strings.HasSuffix(endpointURL.Host, "aliyuncs.com")
178}
179
180// IsAmazonEndpoint - Match if it is exactly Amazon S3 endpoint.
181func IsAmazonEndpoint(endpointURL url.URL) bool {
182 if endpointURL.Host == "s3-external-1.amazonaws.com" || endpointURL.Host == "s3.amazonaws.com" {
183 return true
184 }
185 return GetRegionFromURL(endpointURL) != ""
186}
187
188// IsAmazonGovCloudEndpoint - Match if it is exactly Amazon S3 GovCloud endpoint.
189func IsAmazonGovCloudEndpoint(endpointURL url.URL) bool {
190 if endpointURL == sentinelURL {
191 return false
192 }
193 return (endpointURL.Host == "s3-us-gov-west-1.amazonaws.com" ||
194 endpointURL.Host == "s3-us-gov-east-1.amazonaws.com" ||
195 IsAmazonFIPSGovCloudEndpoint(endpointURL))
196}
197
198// IsAmazonFIPSGovCloudEndpoint - match if the endpoint is FIPS and GovCloud.
199func IsAmazonFIPSGovCloudEndpoint(endpointURL url.URL) bool {
200 if endpointURL == sentinelURL {
201 return false
202 }
203 return IsAmazonFIPSEndpoint(endpointURL) && strings.Contains(endpointURL.Host, "us-gov-")
204}
205
206// IsAmazonFIPSEndpoint - Match if it is exactly Amazon S3 FIPS endpoint.
207// See https://aws.amazon.com/compliance/fips.
208func IsAmazonFIPSEndpoint(endpointURL url.URL) bool {
209 if endpointURL == sentinelURL {
210 return false
211 }
212 return strings.HasPrefix(endpointURL.Host, "s3-fips") && strings.HasSuffix(endpointURL.Host, ".amazonaws.com")
213}
214
215// IsAmazonPrivateLinkEndpoint - Match if it is exactly Amazon S3 PrivateLink interface endpoint
216// See https://docs.aws.amazon.com/AmazonS3/latest/userguide/privatelink-interface-endpoints.html.
217func IsAmazonPrivateLinkEndpoint(endpointURL url.URL) bool {
218 if endpointURL == sentinelURL {
219 return false
220 }
221 return amazonS3HostPrivateLink.MatchString(endpointURL.Host)
222}
223
224// IsGoogleEndpoint - Match if it is exactly Google cloud storage endpoint.
225func IsGoogleEndpoint(endpointURL url.URL) bool {
226 if endpointURL == sentinelURL {
227 return false
228 }
229 return endpointURL.Host == "storage.googleapis.com"
230}
231
232// Expects ascii encoded strings - from output of urlEncodePath
233func percentEncodeSlash(s string) string {
234 return strings.ReplaceAll(s, "/", "%2F")
235}
236
237// QueryEncode - encodes query values in their URL encoded form. In
238// addition to the percent encoding performed by urlEncodePath() used
239// here, it also percent encodes '/' (forward slash)
240func QueryEncode(v url.Values) string {
241 if v == nil {
242 return ""
243 }
244 var buf bytes.Buffer
245 keys := make([]string, 0, len(v))
246 for k := range v {
247 keys = append(keys, k)
248 }
249 sort.Strings(keys)
250 for _, k := range keys {
251 vs := v[k]
252 prefix := percentEncodeSlash(EncodePath(k)) + "="
253 for _, v := range vs {
254 if buf.Len() > 0 {
255 buf.WriteByte('&')
256 }
257 buf.WriteString(prefix)
258 buf.WriteString(percentEncodeSlash(EncodePath(v)))
259 }
260 }
261 return buf.String()
262}
263
264// TagDecode - decodes canonical tag into map of key and value.
265func TagDecode(ctag string) map[string]string {
266 if ctag == "" {
267 return map[string]string{}
268 }
269 tags := strings.Split(ctag, "&")
270 tagMap := make(map[string]string, len(tags))
271 var err error
272 for _, tag := range tags {
273 kvs := strings.SplitN(tag, "=", 2)
274 if len(kvs) == 0 {
275 return map[string]string{}
276 }
277 if len(kvs) == 1 {
278 return map[string]string{}
279 }
280 tagMap[kvs[0]], err = url.PathUnescape(kvs[1])
281 if err != nil {
282 continue
283 }
284 }
285 return tagMap
286}
287
288// TagEncode - encodes tag values in their URL encoded form. In
289// addition to the percent encoding performed by urlEncodePath() used
290// here, it also percent encodes '/' (forward slash)
291func TagEncode(tags map[string]string) string {
292 if tags == nil {
293 return ""
294 }
295 values := url.Values{}
296 for k, v := range tags {
297 values[k] = []string{v}
298 }
299 return QueryEncode(values)
300}
301
302// if object matches reserved string, no need to encode them
303var reservedObjectNames = regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
304
305// EncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
306//
307// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
308// non english characters cannot be parsed due to the nature in which url.Encode() is written
309//
310// This function on the other hand is a direct replacement for url.Encode() technique to support
311// pretty much every UTF-8 character.
312func EncodePath(pathName string) string {
313 if reservedObjectNames.MatchString(pathName) {
314 return pathName
315 }
316 var encodedPathname strings.Builder
317 for _, s := range pathName {
318 if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
319 encodedPathname.WriteRune(s)
320 continue
321 }
322 switch s {
323 case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
324 encodedPathname.WriteRune(s)
325 continue
326 default:
327 l := utf8.RuneLen(s)
328 if l < 0 {
329 // if utf8 cannot convert return the same string as is
330 return pathName
331 }
332 u := make([]byte, l)
333 utf8.EncodeRune(u, s)
334 for _, r := range u {
335 hex := hex.EncodeToString([]byte{r})
336 encodedPathname.WriteString("%" + strings.ToUpper(hex))
337 }
338 }
339 }
340 return encodedPathname.String()
341}
342
343// We support '.' with bucket names but we fallback to using path
344// style requests instead for such buckets.
345var (
346 validBucketName = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9\.\-\_\:]{1,61}[A-Za-z0-9]$`)
347 validBucketNameStrict = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
348 ipAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`)
349)
350
351// Common checker for both stricter and basic validation.
352func checkBucketNameCommon(bucketName string, strict bool) (err error) {
353 if strings.TrimSpace(bucketName) == "" {
354 return errors.New("Bucket name cannot be empty")
355 }
356 if len(bucketName) < 3 {
357 return errors.New("Bucket name cannot be shorter than 3 characters")
358 }
359 if len(bucketName) > 63 {
360 return errors.New("Bucket name cannot be longer than 63 characters")
361 }
362 if ipAddress.MatchString(bucketName) {
363 return errors.New("Bucket name cannot be an ip address")
364 }
365 if strings.Contains(bucketName, "..") || strings.Contains(bucketName, ".-") || strings.Contains(bucketName, "-.") {
366 return errors.New("Bucket name contains invalid characters")
367 }
368 if strict {
369 if !validBucketNameStrict.MatchString(bucketName) {
370 err = errors.New("Bucket name contains invalid characters")
371 }
372 return err
373 }
374 if !validBucketName.MatchString(bucketName) {
375 err = errors.New("Bucket name contains invalid characters")
376 }
377 return err
378}
379
380// CheckValidBucketName - checks if we have a valid input bucket name.
381func CheckValidBucketName(bucketName string) (err error) {
382 return checkBucketNameCommon(bucketName, false)
383}
384
385// CheckValidBucketNameStrict - checks if we have a valid input bucket name.
386// This is a stricter version.
387// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html
388func CheckValidBucketNameStrict(bucketName string) (err error) {
389 return checkBucketNameCommon(bucketName, true)
390}
391
392// CheckValidObjectNamePrefix - checks if we have a valid input object name prefix.
393// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
394func CheckValidObjectNamePrefix(objectName string) error {
395 if len(objectName) > 1024 {
396 return errors.New("Object name cannot be longer than 1024 characters")
397 }
398 if !utf8.ValidString(objectName) {
399 return errors.New("Object name with non UTF-8 strings are not supported")
400 }
401 return nil
402}
403
404// CheckValidObjectName - checks if we have a valid input object name.
405// - http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
406func CheckValidObjectName(objectName string) error {
407 if strings.TrimSpace(objectName) == "" {
408 return errors.New("Object name cannot be empty")
409 }
410 return CheckValidObjectNamePrefix(objectName)
411}