1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.awscore.util; 17 18 import java.util.Optional; 19 import java.util.regex.Matcher; 20 import java.util.regex.Pattern; 21 import software.amazon.awssdk.annotations.SdkProtectedApi; 22 import software.amazon.awssdk.regions.Region; 23 24 @SdkProtectedApi 25 //TODO This isn't used for as many services as it supports. Can we simplify or remove it? 26 public final class AwsHostNameUtils { 27 28 private static final Pattern S3_ENDPOINT_PATTERN = 29 Pattern.compile("^(?:.+\\.)?s3[.-]([a-z0-9-]+)$"); 30 31 private static final Pattern STANDARD_CLOUDSEARCH_ENDPOINT_PATTERN = 32 Pattern.compile("^(?:.+\\.)?([a-z0-9-]+)\\.cloudsearch$"); 33 34 private static final Pattern EXTENDED_CLOUDSEARCH_ENDPOINT_PATTERN = 35 Pattern.compile("^(?:.+\\.)?([a-z0-9-]+)\\.cloudsearch\\..+"); 36 AwsHostNameUtils()37 private AwsHostNameUtils() { 38 } 39 40 /** 41 * Attempts to parse the region name from an endpoint based on conventions 42 * about the endpoint format. 43 * 44 * @param host the hostname to parse 45 * @param serviceHint an optional hint about the service for the endpoint 46 * @return the region parsed from the hostname, or 47 * null if no region information could be found. 48 */ parseSigningRegion(final String host, final String serviceHint)49 public static Optional<Region> parseSigningRegion(final String host, final String serviceHint) { 50 51 if (host == null) { 52 throw new IllegalArgumentException("hostname cannot be null"); 53 } 54 55 if (host.endsWith(".amazonaws.com")) { 56 int index = host.length() - ".amazonaws.com".length(); 57 return parseStandardRegionName(host.substring(0, index)); 58 } 59 60 if (serviceHint != null) { 61 if (serviceHint.equals("cloudsearch") && !host.startsWith("cloudsearch.")) { 62 // CloudSearch domains use the nonstandard domain format 63 // [domain].[region].cloudsearch.[suffix]. 64 65 Matcher matcher = EXTENDED_CLOUDSEARCH_ENDPOINT_PATTERN.matcher(host); 66 if (matcher.matches()) { 67 return Optional.of(Region.of(matcher.group(1))); 68 } 69 } 70 71 // If we have a service hint, look for 'service.[region]' or 72 // 'service-[region]' in the endpoint's hostname. 73 Pattern pattern = Pattern.compile("^(?:.+\\.)?" + Pattern.quote(serviceHint) + "[.-]([a-z0-9-]+)\\."); 74 75 Matcher matcher = pattern.matcher(host); 76 if (matcher.find()) { 77 return Optional.of(Region.of(matcher.group(1))); 78 } 79 } 80 81 // Endpoint is non-standard 82 return Optional.empty(); 83 } 84 85 /** 86 * Parses the region name from a standard (*.amazonaws.com) endpoint. 87 * 88 * @param fragment the portion of the endpoint excluding 89 * ".amazonaws.com" 90 * @return the parsed region name (or "us-east-1" as a 91 * best guess if we can't tell for sure) 92 */ parseStandardRegionName(final String fragment)93 private static Optional<Region> parseStandardRegionName(final String fragment) { 94 Matcher matcher = S3_ENDPOINT_PATTERN.matcher(fragment); 95 if (matcher.matches()) { 96 // host was 'bucket.s3-[region].amazonaws.com'. 97 return Optional.of(Region.of(matcher.group(1))); 98 } 99 100 matcher = STANDARD_CLOUDSEARCH_ENDPOINT_PATTERN.matcher(fragment); 101 if (matcher.matches()) { 102 // host was 'domain.[region].cloudsearch.amazonaws.com'. 103 return Optional.of(Region.of(matcher.group(1))); 104 } 105 106 int index = fragment.lastIndexOf('.'); 107 if (index == -1) { 108 // host was 'service.amazonaws.com', assume they're talking about us-east-1. 109 return Optional.of(Region.US_EAST_1); 110 } 111 112 // host was 'service.[region].amazonaws.com'. 113 String region = fragment.substring(index + 1); 114 115 // Special case for iam.us-gov.amazonaws.com, which is actually 116 // us-gov-west-1. 117 if ("us-gov".equals(region)) { 118 region = "us-gov-west-1"; 119 } 120 121 if ("s3".equals(region)) { 122 region = fragment.substring(index + 4); 123 } 124 125 return Optional.of(Region.of(region)); 126 } 127 } 128