Skip to content

Commit 165ec00

Browse files
committed
Optimize Endpoint Rule Standard library functions
1 parent 27a356b commit 165ec00

File tree

2 files changed

+225
-46
lines changed

2 files changed

+225
-46
lines changed

codegen/src/main/resources/software/amazon/awssdk/codegen/rules2/RuleArn.java.resource

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import java.util.ArrayList;
12
import java.util.Arrays;
23
import java.util.Collections;
34
import java.util.List;
@@ -21,21 +22,58 @@ public final class RuleArn {
2122
}
2223

2324
public static RuleArn parse(String arn) {
24-
String[] base = arn.split(":", 6);
25-
if (base.length != 6) {
25+
if (arn == null || arn.length() < 8 || !arn.startsWith("arn:")) {
2626
return null;
2727
}
28-
// service, resource and `arn` may not be null
29-
if (!base[0].equals("arn")) {
28+
29+
// find each of the first five ':' positions
30+
int p0 = 3; // after "arn"
31+
int p1 = arn.indexOf(':', p0 + 1);
32+
if (p1 < 0) {
3033
return null;
3134
}
32-
if (base[1].isEmpty() || base[2].isEmpty()) {
35+
36+
int p2 = arn.indexOf(':', p1 + 1);
37+
if (p2 < 0) {
3338
return null;
3439
}
35-
if (base[5].isEmpty()) {
40+
41+
int p3 = arn.indexOf(':', p2 + 1);
42+
if (p3 < 0) {
3643
return null;
3744
}
38-
return new RuleArn(base[1], base[2], base[3], base[4], Arrays.asList(base[5].split("[:/]", -1)));
45+
46+
int p4 = arn.indexOf(':', p3 + 1);
47+
if (p4 < 0) {
48+
return null;
49+
}
50+
51+
// extract and validate mandatory parts
52+
String partition = arn.substring(p0 + 1, p1);
53+
String service = arn.substring(p1 + 1, p2);
54+
String region = arn.substring(p2 + 1, p3);
55+
String accountId = arn.substring(p3 + 1, p4);
56+
String resource = arn.substring(p4 + 1);
57+
58+
if (partition.isEmpty() || service.isEmpty() || resource.isEmpty()) {
59+
return null;
60+
}
61+
return new RuleArn(partition, service, region, accountId, splitResource(resource));
62+
}
63+
64+
private static List<String> splitResource(String resource) {
65+
List<String> result = new ArrayList<>();
66+
int start = 0;
67+
int length = resource.length();
68+
for (int i = 0; i < length; i++) {
69+
char c = resource.charAt(i);
70+
if (c == ':' || c == '/') {
71+
result.add(resource.substring(start, i));
72+
start = i + 1;
73+
}
74+
}
75+
result.add(resource.substring(start));
76+
return result;
3977
}
4078

4179
public String partition() {

codegen/src/main/resources/software/amazon/awssdk/codegen/rules2/RulesFunctions.java.resource

Lines changed: 180 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,49 +6,43 @@ import java.util.HashMap;
66
import java.util.List;
77
import java.util.Map;
88
import java.util.function.Supplier;
9-
import java.util.regex.Pattern;
109
import software.amazon.awssdk.annotations.SdkInternalApi;
1110
import software.amazon.awssdk.core.exception.SdkClientException;
1211

1312
@SdkInternalApi
1413
public class RulesFunctions {
15-
private static final Pattern VALID_HOST_LABEL_SUBDOMAINS = Pattern.compile("[a-zA-Z\\d][a-zA-Z\\d\\-.]{0,62}");
16-
private static final Pattern VALID_HOST_LABEL = Pattern.compile("[a-zA-Z\\d][a-zA-Z\\d\\-]{0,62}");
14+
private static final String[] ENCODED_CHARACTERS = { "+", "*", "%7E" };
15+
private static final String[] ENCODED_CHARACTERS_REPLACEMENTS = { "%20", "%2A", "~" };
1716

18-
private static final Pattern VIRTUAL_HOSTABLE_BUCKET = Pattern.compile("[a-z\\d][a-z\\d\\-.]{1,61}[a-z\\d]");
19-
private static final Pattern VIRTUAL_HOSTABLE_BUCKET_NO_SUBDOMAINS = Pattern.compile("[a-z\\d][a-z\\d\\-]{1,61}[a-z\\d]");
20-
private static final Pattern NO_IPS = Pattern.compile("(\\d+\\.){3}\\d+");
21-
private static final Pattern NO_CONSECUTIVE_DASH_OR_DOTS = Pattern.compile(".*[.-]{2}.*");
17+
private static final LazyValue<PartitionData> PARTITION_DATA = LazyValue.<PartitionData> builder()
18+
.initializer(RulesFunctions::loadPartitionData).build();
2219

23-
private static final String[] ENCODED_CHARACTERS = {"+", "*", "%7E"};
24-
private static final String[] ENCODED_CHARACTERS_REPLACEMENTS = {"%20", "%2A", "~"};
20+
private static final LazyValue<Partition> AWS_PARTITION = LazyValue.<Partition> builder()
21+
.initializer(RulesFunctions::findAwsPartition).build();
2522

26-
private static final LazyValue<PartitionData> PARTITION_DATA = LazyValue.<PartitionData>builder()
27-
.initializer(RulesFunctions::loadPartitionData).build();
28-
29-
private static final LazyValue<Partition> AWS_PARTITION = LazyValue.<Partition>builder()
30-
.initializer(RulesFunctions::findAwsPartition).build();
31-
32-
public static String substring(String input, int start, int stop, boolean reverse) {
33-
int len = input.length();
34-
if (start >= stop || len < stop) {
23+
public static String substring(String value, int startIndex, int stopIndex, boolean reverse) {
24+
if (value == null) {
3525
return null;
3626
}
37-
int realStart = start;
38-
int realStop = stop;
39-
if (reverse) {
40-
realStart = len - stop;
41-
realStop = len - start;
27+
28+
int len = value.length();
29+
if (startIndex >= stopIndex || len < stopIndex) {
30+
return null;
4231
}
43-
StringBuilder result = new StringBuilder(realStop - realStart);
44-
for (int idx = realStart; idx < realStop; idx++) {
45-
char ch = input.charAt(idx);
46-
if (ch > 0x7F) {
32+
33+
for (int i = 0; i < len; i++) {
34+
if (value.charAt(i) > 127) {
4735
return null;
4836
}
49-
result.append(ch);
5037
}
51-
return result.toString();
38+
39+
if (reverse) {
40+
int revStart = len - stopIndex;
41+
int revStop = len - startIndex;
42+
return value.substring(revStart, revStop);
43+
} else {
44+
return value.substring(startIndex, stopIndex);
45+
}
5246
}
5347

5448
// URI related functions
@@ -72,11 +66,63 @@ public class RulesFunctions {
7266
}
7367
}
7468

75-
public static boolean isValidHostLabel(String value, boolean allowSubDomains) {
76-
if (allowSubDomains) {
77-
return VALID_HOST_LABEL_SUBDOMAINS.matcher(value).matches();
69+
public static boolean isValidHostLabel(String hostLabel, boolean allowDots) {
70+
int len = hostLabel == null ? 0 : hostLabel.length();
71+
if (len == 0) {
72+
return false;
73+
}
74+
75+
// Single-label mode
76+
if (!allowDots) {
77+
return isValidSingleLabel(hostLabel, 0, len);
78+
}
79+
80+
// Multi-label mode
81+
int start = 0;
82+
for (int i = 0; i <= len; i++) {
83+
if (i == len || hostLabel.charAt(i) == '.') {
84+
// chunk is hostLabel[start..i)
85+
int chunkLen = i - start;
86+
if (chunkLen < 1 || chunkLen > 63) {
87+
return false;
88+
} else if (!isValidSingleLabel(hostLabel, start, i)) {
89+
return false;
90+
}
91+
start = i + 1;
92+
}
7893
}
79-
return VALID_HOST_LABEL.matcher(value).matches();
94+
return true;
95+
}
96+
97+
// Validates a single label in s[start..end): ^[A-Za-z0-9][A-Za-z0-9\-]{0,62}$
98+
private static boolean isValidSingleLabel(String s, int start, int end) {
99+
int length = end - start;
100+
if (length < 1 || length > 63) {
101+
return false;
102+
}
103+
104+
// first char must be [A-Za-z0-9]
105+
if (!isAlphanumeric(s.charAt(start))) {
106+
return false;
107+
}
108+
109+
// remaining chars must be [A-Za-z0-9-]
110+
for (int i = start + 1; i < end; i++) {
111+
char c = s.charAt(i);
112+
if (!isAlphanumeric(c) && c != '-') {
113+
return false;
114+
}
115+
}
116+
117+
return true;
118+
}
119+
120+
private static boolean isAlphanumeric(char c) {
121+
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
122+
}
123+
124+
private static boolean isLowerCaseAlphanumeric(char c) {
125+
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
80126
}
81127

82128
// AWS related functions
@@ -126,13 +172,108 @@ public class RulesFunctions {
126172
return values.get(index);
127173
}
128174

129-
public static boolean awsIsVirtualHostableS3Bucket(String hostLabel, boolean allowSubDomains) {
130-
if (allowSubDomains) {
131-
return VIRTUAL_HOSTABLE_BUCKET.matcher(hostLabel).matches()
132-
// don't allow ip address
133-
&& !NO_IPS.matcher(hostLabel).matches() && !NO_CONSECUTIVE_DASH_OR_DOTS.matcher(hostLabel).matches();
175+
public static boolean awsIsVirtualHostableS3Bucket(String hostLabel, boolean allowDots) {
176+
// Bucket names must be between 3 (min) and 63 (max) characters long.
177+
int bucketLength = hostLabel == null ? 0 : hostLabel.length();
178+
if (bucketLength < 3 || bucketLength > 63) {
179+
return false;
180+
}
181+
182+
// Bucket names must begin and end with a letter or number.
183+
if (!isLowerCaseAlphanumeric(hostLabel.charAt(0)) || !isLowerCaseAlphanumeric(hostLabel.charAt(bucketLength - 1))) {
184+
return false;
185+
}
186+
187+
// Bucket names can consist only of lowercase letters, numbers, periods (.), and hyphens (-).
188+
if (!allowDots) {
189+
for (int i = 1; i < bucketLength - 1; i++) { // already validated 0 and N - 1.
190+
if (!isValidBucketSegmentChar(hostLabel.charAt(i))) {
191+
return false;
192+
}
193+
}
194+
return true;
195+
}
196+
197+
// Check for consecutive dots or hyphens
198+
char last = hostLabel.charAt(0);
199+
for (int i = 1; i < bucketLength; i++) {
200+
char c = hostLabel.charAt(i);
201+
// Don't allow "bucket-.foo" or "bucket.-foo"
202+
if (c == '.') {
203+
if (last == '.' || last == '-') {
204+
return false;
205+
}
206+
} else if (c == '-') {
207+
if (last == '.') {
208+
return false;
209+
}
210+
} else if (!isLowerCaseAlphanumeric(c)) {
211+
return false;
212+
}
213+
last = c;
214+
}
215+
216+
// Bucket names must not be formatted as an IP address (for example, 192.168.5.4).
217+
return !isIpAddr(hostLabel);
218+
}
219+
220+
private static boolean isValidBucketSegmentChar(char c) {
221+
return isLowerCaseAlphanumeric(c) || c == '-';
222+
}
223+
224+
private static boolean isIpAddr(String host) {
225+
if (host == null || host.length() < 2) {
226+
return false;
227+
}
228+
229+
// Simple check for IPv6 (enclosed in square brackets)
230+
if (host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']') {
231+
return true;
232+
}
233+
234+
int from = 0;
235+
int segments = 0;
236+
boolean done = false;
237+
while (!done) {
238+
int index = host.indexOf('.', from);
239+
if (index == -1) {
240+
if (segments != 3) {
241+
// E.g., 123.com
242+
return false;
243+
}
244+
index = host.length();
245+
done = true;
246+
} else if (segments == 3) {
247+
// E.g., 1.2.3.4.5
248+
return false;
249+
}
250+
int length = index - from;
251+
if (length == 1) {
252+
char ch0 = host.charAt(from);
253+
if (ch0 < '0' || ch0 > '9') {
254+
return false;
255+
}
256+
} else if (length == 2) {
257+
char ch0 = host.charAt(from);
258+
char ch1 = host.charAt(from + 1);
259+
if ((ch0 <= '0' || ch0 > '9') || (ch1 < '0' || ch1 > '9')) {
260+
return false;
261+
}
262+
} else if (length == 3) {
263+
char ch0 = host.charAt(from);
264+
char ch1 = host.charAt(from + 1);
265+
char ch2 = host.charAt(from + 2);
266+
if ((ch0 <= '0' || ch0 > '9') || (ch1 < '0' || ch1 > '9') || (ch2 < '0' || ch2 > '9')) {
267+
return false;
268+
}
269+
// This is a heuristic; We are intentionally not checking for the range 000-255.
270+
} else {
271+
return false;
272+
}
273+
from = index + 1;
274+
segments += 1;
134275
}
135-
return VIRTUAL_HOSTABLE_BUCKET_NO_SUBDOMAINS.matcher(hostLabel).matches();
276+
return true;
136277
}
137278

138279
private static PartitionData loadPartitionData() {

0 commit comments

Comments
 (0)