Skip to content

Commit 6f1a144

Browse files
authored
Add generic AWS S3 domain support (#1470)
Signed-off-by: Bala.FA <bala@minio.io>
1 parent 0a04661 commit 6f1a144

5 files changed

Lines changed: 293 additions & 197 deletions

File tree

api/src/main/java/io/minio/BucketArgs.java

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package io.minio;
1818

19+
import io.minio.http.HttpUtils;
20+
import io.minio.org.apache.commons.validator.routines.InetAddressValidator;
1921
import java.util.Objects;
22+
import java.util.regex.Pattern;
2023

2124
/** Base argument class holds bucket name and region. */
2225
public abstract class BucketArgs extends BaseArgs {
@@ -34,32 +37,35 @@ public String region() {
3437
/** Base argument builder class for {@link BucketArgs}. */
3538
public abstract static class Builder<B extends Builder<B, A>, A extends BucketArgs>
3639
extends BaseArgs.Builder<B, A> {
40+
private static final Pattern BUCKET_NAME_REGEX =
41+
Pattern.compile("^[a-z0-9][a-z0-9\\.\\-]{1,61}[a-z0-9]$");
42+
3743
protected void validateBucketName(String name) {
3844
validateNotNull(name, "bucket name");
3945

40-
// Bucket names cannot be no less than 3 and no more than 63 characters long.
41-
if (name.length() < 3 || name.length() > 63) {
46+
if (!BUCKET_NAME_REGEX.matcher(name).find()) {
4247
throw new IllegalArgumentException(
43-
name + " : " + "bucket name must be at least 3 and no more than 63 characters long");
48+
"bucket name '"
49+
+ name
50+
+ "' does not follow Amazon S3 standards. For more information refer "
51+
+ "https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html");
4452
}
45-
// Successive periods in bucket names are not allowed.
46-
if (name.contains("..")) {
47-
String msg =
48-
"bucket name cannot contain successive periods. For more information refer "
49-
+ "http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html";
50-
throw new IllegalArgumentException(name + " : " + msg);
53+
54+
if (InetAddressValidator.getInstance().isValidInet4Address(name)) {
55+
throw new IllegalArgumentException(
56+
"bucket name '" + name + "' must not be formatted as an IP address");
5157
}
52-
// Bucket names should be dns compatible.
53-
if (!name.matches("^[a-z0-9][a-z0-9\\.\\-]+[a-z0-9]$")) {
54-
String msg =
55-
"bucket name does not follow Amazon S3 standards. For more information refer "
56-
+ "http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html";
57-
throw new IllegalArgumentException(name + " : " + msg);
58+
59+
if (name.contains("..") || name.contains(".-") || name.contains("-.")) {
60+
throw new IllegalArgumentException(
61+
"bucket name '" + name + "' cannot contain successive characters '..', '.-' and '-.'");
5862
}
5963
}
6064

6165
private void validateRegion(String region) {
62-
validateNullOrNotEmptyString(region, "region");
66+
if (region != null && !HttpUtils.REGION_REGEX.matcher(region).find()) {
67+
throw new IllegalArgumentException("invalid region " + region);
68+
}
6369
}
6470

6571
@Override

api/src/main/java/io/minio/MinioAsyncClient.java

Lines changed: 65 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import java.nio.file.StandardOpenOption;
6767
import java.security.InvalidKeyException;
6868
import java.security.NoSuchAlgorithmException;
69+
import java.util.Arrays;
6970
import java.util.Date;
7071
import java.util.Iterator;
7172
import java.util.LinkedList;
@@ -75,6 +76,7 @@
7576
import java.util.concurrent.CompletableFuture;
7677
import java.util.concurrent.CompletionException;
7778
import java.util.concurrent.ExecutionException;
79+
import java.util.regex.Matcher;
7880
import okhttp3.HttpUrl;
7981
import okhttp3.OkHttpClient;
8082
import okhttp3.Request;
@@ -131,22 +133,20 @@
131133
public class MinioAsyncClient extends S3Base {
132134
private MinioAsyncClient(
133135
HttpUrl baseUrl,
134-
String region,
135-
boolean isAwsHost,
136-
boolean isFipsHost,
137-
boolean isAccelerateHost,
138-
boolean isDualStackHost,
136+
String awsS3Prefix,
137+
String awsDomainSuffix,
138+
boolean awsDualstack,
139139
boolean useVirtualStyle,
140+
String region,
140141
Provider provider,
141142
OkHttpClient httpClient) {
142143
super(
143144
baseUrl,
144-
region,
145-
isAwsHost,
146-
isFipsHost,
147-
isAccelerateHost,
148-
isDualStackHost,
145+
awsS3Prefix,
146+
awsDomainSuffix,
147+
awsDualstack,
149148
useVirtualStyle,
149+
region,
150150
provider,
151151
httpClient);
152152
}
@@ -3213,87 +3213,66 @@ public static Builder builder() {
32133213
/** Argument builder of {@link MinioClient}. */
32143214
public static final class Builder {
32153215
private HttpUrl baseUrl;
3216-
private String region;
3217-
private boolean isAwsHost;
3218-
private boolean isFipsHost;
3219-
private boolean isAccelerateHost;
3220-
private boolean isDualStackHost;
3216+
private String awsS3Prefix;
3217+
private String awsDomainSuffix;
3218+
private boolean awsDualstack;
32213219
private boolean useVirtualStyle;
3220+
3221+
private String region;
32223222
private Provider provider;
32233223
private OkHttpClient httpClient;
32243224

3225-
private boolean isAwsChinaHost;
3226-
private String regionInUrl;
3225+
private void setAwsInfo(String host, boolean https) {
3226+
this.awsS3Prefix = null;
3227+
this.awsDomainSuffix = null;
3228+
this.awsDualstack = false;
32273229

3228-
private boolean isAwsFipsEndpoint(String endpoint) {
3229-
return endpoint.startsWith("s3-fips.");
3230-
}
3230+
if (!HttpUtils.HOSTNAME_REGEX.matcher(host).find()) return;
32313231

3232-
private boolean isAwsAccelerateEndpoint(String endpoint) {
3233-
return endpoint.startsWith("s3-accelerate.");
3234-
}
3232+
if (HttpUtils.AWS_ELB_ENDPOINT_REGEX.matcher(host).find()) {
3233+
String[] tokens = host.split("\\.elb\\.amazonaws\\.com", 1)[0].split("\\.");
3234+
this.region = tokens[tokens.length - 1];
3235+
return;
3236+
}
32353237

3236-
private boolean isAwsEndpoint(String endpoint) {
3237-
return (endpoint.startsWith("s3.")
3238-
|| isAwsFipsEndpoint(endpoint)
3239-
|| isAwsAccelerateEndpoint(endpoint))
3240-
&& (endpoint.endsWith(".amazonaws.com") || endpoint.endsWith(".amazonaws.com.cn"));
3241-
}
3238+
if (!HttpUtils.AWS_ENDPOINT_REGEX.matcher(host).find()) return;
32423239

3243-
private boolean isAwsDualStackEndpoint(String endpoint) {
3244-
return endpoint.contains(".dualstack.");
3245-
}
3240+
if (!HttpUtils.AWS_S3_ENDPOINT_REGEX.matcher(host).find()) {
3241+
throw new IllegalArgumentException("invalid Amazon AWS host " + host);
3242+
}
3243+
3244+
Matcher matcher = HttpUtils.AWS_S3_PREFIX_REGEX.matcher(host);
3245+
matcher.lookingAt();
3246+
int end = matcher.end();
3247+
3248+
this.awsS3Prefix = host.substring(0, end);
3249+
if (this.awsS3Prefix.contains("s3-accesspoint") && !https) {
3250+
throw new IllegalArgumentException("use HTTPS scheme for host " + host);
3251+
}
32463252

3247-
/**
3248-
* Extracts region from AWS endpoint if available. Region is placed at second token normal
3249-
* endpoints and third token for dualstack endpoints.
3250-
*
3251-
* <p>Region is marked in square brackets in below examples.
3252-
* <pre>
3253-
* https://s3.[us-east-2].amazonaws.com
3254-
* https://s3.dualstack.[ca-central-1].amazonaws.com
3255-
* https://s3.[cn-north-1].amazonaws.com.cn
3256-
* https://s3.dualstack.[cn-northwest-1].amazonaws.com.cn
3257-
*/
3258-
private String extractRegion(String endpoint) {
3259-
String[] tokens = endpoint.split("\\.");
3260-
String token = tokens[1];
3261-
3262-
// If token is "dualstack", then region might be in next token.
3263-
if (token.equals("dualstack")) {
3264-
token = tokens[2];
3253+
String[] tokens = host.substring(end).split("\\.");
3254+
awsDualstack = "dualstack".equals(tokens[0]);
3255+
if (awsDualstack) tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
3256+
String regionInHost = null;
3257+
if (!tokens[0].equals("vpce") && !tokens[0].equals("amazonaws")) {
3258+
regionInHost = tokens[0];
3259+
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
32653260
}
3261+
this.awsDomainSuffix = String.join(".", tokens);
32663262

3267-
// If token is equal to "amazonaws", region is not passed in the endpoint.
3268-
if (token.equals("amazonaws")) {
3269-
return null;
3263+
if (host.equals("s3-external-1.amazonaws.com")) regionInHost = "us-east-1";
3264+
if (host.equals("s3-us-gov-west-1.amazonaws.com")
3265+
|| host.equals("s3-fips-us-gov-west-1.amazonaws.com")) {
3266+
regionInHost = "us-gov-west-1";
32703267
}
32713268

3272-
// Return token as region.
3273-
return token;
3269+
if (regionInHost != null) this.region = regionInHost;
32743270
}
32753271

32763272
private void setBaseUrl(HttpUrl url) {
3277-
String host = url.host();
3278-
3279-
this.isAwsHost = isAwsEndpoint(host);
3280-
this.isAwsChinaHost = false;
3281-
if (this.isAwsHost) {
3282-
this.isAwsChinaHost = host.endsWith(".cn");
3283-
url =
3284-
url.newBuilder()
3285-
.host(this.isAwsChinaHost ? "amazonaws.com.cn" : "amazonaws.com")
3286-
.build();
3287-
this.isFipsHost = isAwsFipsEndpoint(host);
3288-
this.isAccelerateHost = isAwsAccelerateEndpoint(host);
3289-
this.isDualStackHost = isAwsDualStackEndpoint(host);
3290-
this.regionInUrl = extractRegion(host);
3291-
this.useVirtualStyle = true;
3292-
} else {
3293-
this.useVirtualStyle = host.endsWith("aliyuncs.com");
3294-
}
3295-
32963273
this.baseUrl = url;
3274+
this.setAwsInfo(url.host(), url.isHttps());
3275+
this.useVirtualStyle = this.awsDomainSuffix != null || url.host().endsWith("aliyuncs.com");
32973276
}
32983277

32993278
public Builder endpoint(String endpoint) {
@@ -3325,8 +3304,9 @@ public Builder endpoint(HttpUrl url) {
33253304
}
33263305

33273306
public Builder region(String region) {
3328-
HttpUtils.validateNullOrNotEmptyString(region, "region");
3329-
this.regionInUrl = region;
3307+
if (region != null && !HttpUtils.REGION_REGEX.matcher(region).find()) {
3308+
throw new IllegalArgumentException("invalid region " + region);
3309+
}
33303310
this.region = region;
33313311
return this;
33323312
}
@@ -3349,7 +3329,11 @@ public Builder httpClient(OkHttpClient httpClient) {
33493329

33503330
public MinioAsyncClient build() {
33513331
HttpUtils.validateNotNull(this.baseUrl, "endpoint");
3352-
if (this.isAwsChinaHost && this.regionInUrl == null && this.region == null) {
3332+
3333+
if (this.awsDomainSuffix != null
3334+
&& this.awsDomainSuffix.endsWith(".cn")
3335+
&& !this.awsS3Prefix.endsWith("s3-accelerate.")
3336+
&& this.region == null) {
33533337
throw new IllegalArgumentException(
33543338
"Region missing in Amazon S3 China endpoint " + this.baseUrl);
33553339
}
@@ -3358,17 +3342,15 @@ public MinioAsyncClient build() {
33583342
this.httpClient =
33593343
HttpUtils.newDefaultHttpClient(
33603344
DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT);
3361-
if (this.region == null) this.region = regionInUrl;
33623345
}
33633346

33643347
return new MinioAsyncClient(
33653348
baseUrl,
3366-
region,
3367-
isAwsHost,
3368-
isFipsHost,
3369-
isAccelerateHost,
3370-
isDualStackHost,
3349+
awsS3Prefix,
3350+
awsDomainSuffix,
3351+
awsDualstack,
33713352
useVirtualStyle,
3353+
region,
33723354
provider,
33733355
httpClient);
33743356
}

api/src/main/java/io/minio/MinioClient.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2405,12 +2405,22 @@ public void traceOff() throws IOException {
24052405
asyncClient.traceOff();
24062406
}
24072407

2408-
/** Enables accelerate endpoint for Amazon S3 endpoint. */
2408+
/**
2409+
* Enables accelerate endpoint for Amazon S3 endpoint.
2410+
*
2411+
* @deprecated This method is no longer supported.
2412+
*/
2413+
@Deprecated
24092414
public void enableAccelerateEndpoint() {
24102415
asyncClient.enableAccelerateEndpoint();
24112416
}
24122417

2413-
/** Disables accelerate endpoint for Amazon S3 endpoint. */
2418+
/**
2419+
* Disables accelerate endpoint for Amazon S3 endpoint.
2420+
*
2421+
* @deprecated This method is no longer supported.
2422+
*/
2423+
@Deprecated
24142424
public void disableAccelerateEndpoint() {
24152425
asyncClient.disableAccelerateEndpoint();
24162426
}
@@ -2435,6 +2445,11 @@ public void disableVirtualStyleEndpoint() {
24352445
asyncClient.disableVirtualStyleEndpoint();
24362446
}
24372447

2448+
/** Sets AWS S3 domain prefix. */
2449+
public void setAwsS3Prefix(String awsS3Prefix) {
2450+
asyncClient.setAwsS3Prefix(awsS3Prefix);
2451+
}
2452+
24382453
public static Builder builder() {
24392454
return new Builder();
24402455
}

0 commit comments

Comments
 (0)