@@ -6,49 +6,47 @@ 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() ;
23+ private static final int MAX_HOST_LABEL_SIZE = 63;
24+ private static final int MIN_BUCKET_SIZE = 3 ;
2825
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) {
26+ public static String substring(String value, int startIndex, int stopIndex, boolean reverse) {
27+ if (value == null) {
3528 return null;
3629 }
37- int realStart = start;
38- int realStop = stop;
39- if (reverse) {
40- realStart = len - stop;
41- realStop = len - start;
30+
31+ int len = value.length();
32+ if (startIndex >= stopIndex || len < stopIndex) {
33+ return null;
4234 }
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 ) {
35+
36+ for (int i = 0; i < len; i ++) {
37+ // non-ascii characters (values outside of the 7bit ASCII range)
38+ if (value.charAt(i) > 127 ) {
4739 return null;
4840 }
49- result.append(ch);
5041 }
51- return result.toString();
42+
43+ if (reverse) {
44+ int revStart = len - stopIndex;
45+ int revStop = len - startIndex;
46+ return value.substring(revStart, revStop);
47+ } else {
48+ return value.substring(startIndex, stopIndex);
49+ }
5250 }
5351
5452 // URI related functions
@@ -72,11 +70,63 @@ public class RulesFunctions {
7270 }
7371 }
7472
75- public static boolean isValidHostLabel(String value, boolean allowSubDomains) {
76- if (allowSubDomains) {
77- return VALID_HOST_LABEL_SUBDOMAINS.matcher(value).matches();
73+ public static boolean isValidHostLabel(String hostLabel, boolean allowDots) {
74+ int len = hostLabel == null ? 0 : hostLabel.length();
75+ if (len == 0) {
76+ return false;
77+ }
78+
79+ // Single-label mode
80+ if (!allowDots) {
81+ return isValidSingleLabel(hostLabel, 0, len);
82+ }
83+
84+ // Multi-label mode
85+ int start = 0;
86+ for (int i = 0; i <= len; i++) {
87+ if (i == len || hostLabel.charAt(i) == '.') {
88+ // chunk is hostLabel[start..i)
89+ int chunkLen = i - start;
90+ if (chunkLen < 1 || chunkLen > MAX_HOST_LABEL_SIZE) {
91+ return false;
92+ } else if (!isValidSingleLabel(hostLabel, start, i)) {
93+ return false;
94+ }
95+ start = i + 1;
96+ }
97+ }
98+ return true;
99+ }
100+
101+ // Validates a single label in s[start..end): ^[A-Za-z0-9][A-Za-z0-9\-]{0,62}$
102+ private static boolean isValidSingleLabel(String s, int start, int end) {
103+ int length = end - start;
104+ if (length < 1 || length > MAX_HOST_LABEL_SIZE) {
105+ return false;
106+ }
107+
108+ // first char must be [A-Za-z0-9]
109+ if (!isAlphanumeric(s.charAt(start))) {
110+ return false;
78111 }
79- return VALID_HOST_LABEL.matcher(value).matches();
112+
113+ // remaining chars must be [A-Za-z0-9-]
114+ for (int i = start + 1; i < end; i++) {
115+ char c = s.charAt(i);
116+ if (!isAlphanumeric(c) && c != '-') {
117+ return false;
118+ }
119+ }
120+
121+ return true;
122+ }
123+
124+ private static boolean isAlphanumeric(char c) {
125+ return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
126+ }
127+
128+ private static boolean isLowerCaseAlphanumeric(char c) {
129+ return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
80130 }
81131
82132 // AWS related functions
@@ -126,13 +176,108 @@ public class RulesFunctions {
126176 return values.get(index);
127177 }
128178
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();
179+ public static boolean awsIsVirtualHostableS3Bucket(String hostLabel, boolean allowDots) {
180+ // Bucket names must be between 3 (min) and 63 (max) characters long.
181+ int bucketLength = hostLabel == null ? 0 : hostLabel.length();
182+ if (bucketLength < MIN_BUCKET_SIZE || bucketLength > MAX_HOST_LABEL_SIZE) {
183+ return false;
184+ }
185+
186+ // Bucket names must begin and end with a letter or number.
187+ if (!isLowerCaseAlphanumeric(hostLabel.charAt(0)) || !isLowerCaseAlphanumeric(hostLabel.charAt(bucketLength - 1))) {
188+ return false;
189+ }
190+
191+ // Bucket names can consist only of lowercase letters, numbers, periods (.), and hyphens (-).
192+ if (!allowDots) {
193+ for (int i = 1; i < bucketLength - 1; i++) { // already validated 0 and N - 1.
194+ if (!isValidBucketSegmentChar(hostLabel.charAt(i))) {
195+ return false;
196+ }
197+ }
198+ return true;
199+ }
200+
201+ // Check for consecutive dots or hyphens
202+ char last = hostLabel.charAt(0);
203+ for (int i = 1; i < bucketLength; i++) {
204+ char c = hostLabel.charAt(i);
205+ // Don't allow "bucket-.foo" or "bucket.-foo"
206+ if (c == '.') {
207+ if (last == '.' || last == '-') {
208+ return false;
209+ }
210+ } else if (c == '-') {
211+ if (last == '.') {
212+ return false;
213+ }
214+ } else if (!isLowerCaseAlphanumeric(c)) {
215+ return false;
216+ }
217+ last = c;
218+ }
219+
220+ // Bucket names must not be formatted as an IP address (for example, 192.168.5.4).
221+ return !isIpAddr(hostLabel);
222+ }
223+
224+ private static boolean isValidBucketSegmentChar(char c) {
225+ return isLowerCaseAlphanumeric(c) || c == '-';
226+ }
227+
228+ private static boolean isIpAddr(String host) {
229+ if (host == null || host.length() < 2) {
230+ return false;
231+ }
232+
233+ // Simple check for IPv6 (enclosed in square brackets)
234+ if (host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']') {
235+ return true;
236+ }
237+
238+ int from = 0;
239+ int segments = 0;
240+ boolean done = false;
241+ while (!done) {
242+ int index = host.indexOf('.', from);
243+ if (index == -1) {
244+ if (segments != 3) {
245+ // E.g., 123.com
246+ return false;
247+ }
248+ index = host.length();
249+ done = true;
250+ } else if (segments == 3) {
251+ // E.g., 1.2.3.4.5
252+ return false;
253+ }
254+ int length = index - from;
255+ if (length == 1) {
256+ char ch0 = host.charAt(from);
257+ if (ch0 < '0' || ch0 > '9') {
258+ return false;
259+ }
260+ } else if (length == 2) {
261+ char ch0 = host.charAt(from);
262+ char ch1 = host.charAt(from + 1);
263+ if ((ch0 <= '0' || ch0 > '9') || (ch1 < '0' || ch1 > '9')) {
264+ return false;
265+ }
266+ } else if (length == 3) {
267+ char ch0 = host.charAt(from);
268+ char ch1 = host.charAt(from + 1);
269+ char ch2 = host.charAt(from + 2);
270+ if ((ch0 <= '0' || ch0 > '9') || (ch1 < '0' || ch1 > '9') || (ch2 < '0' || ch2 > '9')) {
271+ return false;
272+ }
273+ // This is a heuristic; We are intentionally not checking for the range 000-255.
274+ } else {
275+ return false;
276+ }
277+ from = index + 1;
278+ segments += 1;
134279 }
135- return VIRTUAL_HOSTABLE_BUCKET_NO_SUBDOMAINS.matcher(hostLabel).matches() ;
280+ return true ;
136281 }
137282
138283 private static PartitionData loadPartitionData() {
0 commit comments