Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand All @@ -21,21 +22,58 @@ public final class RuleArn {
}

public static RuleArn parse(String arn) {
String[] base = arn.split(":", 6);
if (base.length != 6) {
if (arn == null || arn.length() < 8 || !arn.startsWith("arn:")) {
return null;
}
// service, resource and `arn` may not be null
if (!base[0].equals("arn")) {

// find each of the first five ':' positions
int p0 = 3; // after "arn"
int p1 = arn.indexOf(':', p0 + 1);
if (p1 < 0) {
return null;
}
if (base[1].isEmpty() || base[2].isEmpty()) {

int p2 = arn.indexOf(':', p1 + 1);
if (p2 < 0) {
return null;
}
if (base[5].isEmpty()) {

int p3 = arn.indexOf(':', p2 + 1);
if (p3 < 0) {
return null;
}
return new RuleArn(base[1], base[2], base[3], base[4], Arrays.asList(base[5].split("[:/]", -1)));

int p4 = arn.indexOf(':', p3 + 1);
if (p4 < 0) {
return null;
}

// extract and validate mandatory parts
String partition = arn.substring(p0 + 1, p1);
String service = arn.substring(p1 + 1, p2);
String region = arn.substring(p2 + 1, p3);
String accountId = arn.substring(p3 + 1, p4);
String resource = arn.substring(p4 + 1);

if (partition.isEmpty() || service.isEmpty() || resource.isEmpty()) {
return null;
}
return new RuleArn(partition, service, region, accountId, splitResource(resource));
}

private static List<String> splitResource(String resource) {
List<String> result = new ArrayList<>();
int start = 0;
int length = resource.length();
for (int i = 0; i < length; i++) {
char c = resource.charAt(i);
if (c == ':' || c == '/') {
result.add(resource.substring(start, i));
start = i + 1;
}
}
result.add(resource.substring(start));
return result;
}

public String partition() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,43 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.exception.SdkClientException;

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

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

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

private static final LazyValue<PartitionData> PARTITION_DATA = LazyValue.<PartitionData>builder()
.initializer(RulesFunctions::loadPartitionData).build();

private static final LazyValue<Partition> AWS_PARTITION = LazyValue.<Partition>builder()
.initializer(RulesFunctions::findAwsPartition).build();

public static String substring(String input, int start, int stop, boolean reverse) {
int len = input.length();
if (start >= stop || len < stop) {
public static String substring(String value, int startIndex, int stopIndex, boolean reverse) {
if (value == null) {
return null;
}
int realStart = start;
int realStop = stop;
if (reverse) {
realStart = len - stop;
realStop = len - start;

int len = value.length();
if (startIndex >= stopIndex || len < stopIndex) {
return null;
}
StringBuilder result = new StringBuilder(realStop - realStart);
for (int idx = realStart; idx < realStop; idx++) {
char ch = input.charAt(idx);
if (ch > 0x7F) {

for (int i = 0; i < len; i++) {
if (value.charAt(i) > 127) {
return null;
}
result.append(ch);
}
return result.toString();

if (reverse) {
int revStart = len - stopIndex;
int revStop = len - startIndex;
return value.substring(revStart, revStop);
} else {
return value.substring(startIndex, stopIndex);
}
}

// URI related functions
Expand All @@ -72,11 +66,63 @@ public class RulesFunctions {
}
}

public static boolean isValidHostLabel(String value, boolean allowSubDomains) {
if (allowSubDomains) {
return VALID_HOST_LABEL_SUBDOMAINS.matcher(value).matches();
public static boolean isValidHostLabel(String hostLabel, boolean allowDots) {
int len = hostLabel == null ? 0 : hostLabel.length();
if (len == 0) {
return false;
}

// Single-label mode
if (!allowDots) {
return isValidSingleLabel(hostLabel, 0, len);
}

// Multi-label mode
int start = 0;
for (int i = 0; i <= len; i++) {
if (i == len || hostLabel.charAt(i) == '.') {
// chunk is hostLabel[start..i)
int chunkLen = i - start;
if (chunkLen < 1 || chunkLen > 63) {
return false;
} else if (!isValidSingleLabel(hostLabel, start, i)) {
return false;
}
start = i + 1;
}
}
return VALID_HOST_LABEL.matcher(value).matches();
return true;
}

// Validates a single label in s[start..end): ^[A-Za-z0-9][A-Za-z0-9\-]{0,62}$
private static boolean isValidSingleLabel(String s, int start, int end) {
int length = end - start;
if (length < 1 || length > 63) {
return false;
}

// first char must be [A-Za-z0-9]
if (!isAlphanumeric(s.charAt(start))) {
return false;
}

// remaining chars must be [A-Za-z0-9-]
for (int i = start + 1; i < end; i++) {
char c = s.charAt(i);
if (!isAlphanumeric(c) && c != '-') {
return false;
}
}

return true;
}

private static boolean isAlphanumeric(char c) {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}

private static boolean isLowerCaseAlphanumeric(char c) {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
}

// AWS related functions
Expand Down Expand Up @@ -126,13 +172,108 @@ public class RulesFunctions {
return values.get(index);
}

public static boolean awsIsVirtualHostableS3Bucket(String hostLabel, boolean allowSubDomains) {
if (allowSubDomains) {
return VIRTUAL_HOSTABLE_BUCKET.matcher(hostLabel).matches()
// don't allow ip address
&& !NO_IPS.matcher(hostLabel).matches() && !NO_CONSECUTIVE_DASH_OR_DOTS.matcher(hostLabel).matches();
public static boolean awsIsVirtualHostableS3Bucket(String hostLabel, boolean allowDots) {
// Bucket names must be between 3 (min) and 63 (max) characters long.
int bucketLength = hostLabel == null ? 0 : hostLabel.length();
if (bucketLength < 3 || bucketLength > 63) {
return false;
}

// Bucket names must begin and end with a letter or number.
if (!isLowerCaseAlphanumeric(hostLabel.charAt(0)) || !isLowerCaseAlphanumeric(hostLabel.charAt(bucketLength - 1))) {
return false;
}

// Bucket names can consist only of lowercase letters, numbers, periods (.), and hyphens (-).
if (!allowDots) {
for (int i = 1; i < bucketLength - 1; i++) { // already validated 0 and N - 1.
if (!isValidBucketSegmentChar(hostLabel.charAt(i))) {
return false;
}
}
return true;
}

// Check for consecutive dots or hyphens
char last = hostLabel.charAt(0);
for (int i = 1; i < bucketLength; i++) {
char c = hostLabel.charAt(i);
// Don't allow "bucket-.foo" or "bucket.-foo"
if (c == '.') {
if (last == '.' || last == '-') {
return false;
}
} else if (c == '-') {
if (last == '.') {
return false;
}
} else if (!isLowerCaseAlphanumeric(c)) {
return false;
}
last = c;
}

// Bucket names must not be formatted as an IP address (for example, 192.168.5.4).
return !isIpAddr(hostLabel);
}

private static boolean isValidBucketSegmentChar(char c) {
return isLowerCaseAlphanumeric(c) || c == '-';
}

private static boolean isIpAddr(String host) {
if (host == null || host.length() < 2) {
return false;
}

// Simple check for IPv6 (enclosed in square brackets)
if (host.charAt(0) == '[' && host.charAt(host.length() - 1) == ']') {
return true;
}

int from = 0;
int segments = 0;
boolean done = false;
while (!done) {
int index = host.indexOf('.', from);
if (index == -1) {
if (segments != 3) {
// E.g., 123.com
return false;
}
index = host.length();
done = true;
} else if (segments == 3) {
// E.g., 1.2.3.4.5
return false;
}
int length = index - from;
if (length == 1) {
char ch0 = host.charAt(from);
if (ch0 < '0' || ch0 > '9') {
return false;
}
} else if (length == 2) {
char ch0 = host.charAt(from);
char ch1 = host.charAt(from + 1);
if ((ch0 <= '0' || ch0 > '9') || (ch1 < '0' || ch1 > '9')) {
return false;
}
} else if (length == 3) {
char ch0 = host.charAt(from);
char ch1 = host.charAt(from + 1);
char ch2 = host.charAt(from + 2);
if ((ch0 <= '0' || ch0 > '9') || (ch1 < '0' || ch1 > '9') || (ch2 < '0' || ch2 > '9')) {
return false;
}
// This is a heuristic; We are intentionally not checking for the range 000-255.
} else {
return false;
}
from = index + 1;
segments += 1;
}
return VIRTUAL_HOSTABLE_BUCKET_NO_SUBDOMAINS.matcher(hostLabel).matches();
return true;
}

private static PartitionData loadPartitionData() {
Expand Down
Loading