@@ -6,49 +6,43 @@ import java.util.HashMap;
66import java.util.List;
77import java.util.Map;
88import java.util.function.Supplier;
9- import java.util.regex.Pattern;
109import software.amazon.awssdk.annotations.SdkInternalApi;
1110import software.amazon.awssdk.core.exception.SdkClientException;
1211
1312@SdkInternalApi
1413public 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