| 1 |
<?php |
| 2 |
|
| 3 |
namespace Dudlewebs\WPMCS\s3\Aws\EndpointV2\Ruleset; |
| 4 |
|
| 5 |
use Dudlewebs\WPMCS\s3\Aws\Exception\UnresolvedEndpointException; |
| 6 |
/** |
| 7 |
* Provides functions and actions to be performed for endpoint evaluation. |
| 8 |
* This is an internal only class and is not subject to backwards-compatibility guarantees. |
| 9 |
* |
| 10 |
* @internal |
| 11 |
*/ |
| 12 |
class RulesetStandardLibrary |
| 13 |
{ |
| 14 |
const IPV4_RE = '/^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$/'; |
| 15 |
const IPV6_RE = '/([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:| |
| 16 |
. ([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F] |
| 17 |
. {1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:) |
| 18 |
. {1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}| |
| 19 |
. [0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80: |
| 20 |
. (:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]| |
| 21 |
. 1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F] |
| 22 |
. {1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4] |
| 23 |
. |1{0,1}[0-9]){0,1}[0-9])/'; |
| 24 |
const TEMPLATE_ESCAPE_RE = '/{\\{\\s*(.*?)\\s*\\}\\}/'; |
| 25 |
const TEMPLATE_SEARCH_RE = '/\\{[a-zA-Z#]+\\}/'; |
| 26 |
const TEMPLATE_PARSE_RE = '#\\{((?>[^\\{\\}]+)|(?R))*\\}#x'; |
| 27 |
const HOST_LABEL_RE = '/^(?!-)[a-zA-Z\\d-]{1,63}(?<!-)$/'; |
| 28 |
private $partitions; |
| 29 |
public function __construct($partitions) |
| 30 |
{ |
| 31 |
$this->partitions = $partitions; |
| 32 |
} |
| 33 |
/** |
| 34 |
* Determines if a value is set. |
| 35 |
* |
| 36 |
* @return boolean |
| 37 |
*/ |
| 38 |
public function is_set($value) |
| 39 |
{ |
| 40 |
return isset($value); |
| 41 |
} |
| 42 |
/** |
| 43 |
* Function implementation of logical operator `not` |
| 44 |
* |
| 45 |
* @return boolean |
| 46 |
*/ |
| 47 |
public function not($value) |
| 48 |
{ |
| 49 |
return !$value; |
| 50 |
} |
| 51 |
/** |
| 52 |
* Find an attribute within a value given a path string. |
| 53 |
* |
| 54 |
* @return mixed |
| 55 |
*/ |
| 56 |
public function getAttr($from, $path) |
| 57 |
{ |
| 58 |
// Handles the case where "[<int|string]" is provided as the top-level path |
| 59 |
if (\preg_match('/^\\[(\\w+)\\]$/', $path, $matches)) { |
| 60 |
$index = \is_numeric($matches[1]) ? (int) $matches[1] : $matches[1]; |
| 61 |
return $from[$index] ?? null; |
| 62 |
} |
| 63 |
$parts = \explode('.', $path); |
| 64 |
foreach ($parts as $part) { |
| 65 |
$sliceIdx = \strpos($part, '['); |
| 66 |
if ($sliceIdx !== \false) { |
| 67 |
if (\substr($part, -1) !== ']') { |
| 68 |
return null; |
| 69 |
} |
| 70 |
$slice = (int) \substr($part, $sliceIdx + 1, \strlen($part) - 1); |
| 71 |
$fromIndex = \substr($part, 0, $sliceIdx); |
| 72 |
$from = $from[$fromIndex][$slice] ?? null; |
| 73 |
} else { |
| 74 |
$from = $from[$part]; |
| 75 |
} |
| 76 |
} |
| 77 |
return $from; |
| 78 |
} |
| 79 |
/** |
| 80 |
* Computes a substring given the start index and end index. If `reverse` is |
| 81 |
* true, slice the string from the end instead. |
| 82 |
* |
| 83 |
* @return mixed |
| 84 |
*/ |
| 85 |
public function substring($input, $start, $stop, $reverse) |
| 86 |
{ |
| 87 |
if (!\is_string($input)) { |
| 88 |
throw new UnresolvedEndpointException('Input passed to `substring` must be `string`.'); |
| 89 |
} |
| 90 |
if (\preg_match('/[^\\x00-\\x7F]/', $input)) { |
| 91 |
return null; |
| 92 |
} |
| 93 |
if ($start >= $stop or \strlen($input) < $stop) { |
| 94 |
return null; |
| 95 |
} |
| 96 |
if (!$reverse) { |
| 97 |
return \substr($input, $start, $stop - $start); |
| 98 |
} else { |
| 99 |
$offset = \strlen($input) - $stop; |
| 100 |
$length = $stop - $start; |
| 101 |
return \substr($input, $offset, $length); |
| 102 |
} |
| 103 |
} |
| 104 |
/** |
| 105 |
* Evaluates two strings for equality. |
| 106 |
* |
| 107 |
* @return boolean |
| 108 |
*/ |
| 109 |
public function stringEquals($string1, $string2) |
| 110 |
{ |
| 111 |
if (!\is_string($string1) || !\is_string($string2)) { |
| 112 |
throw new UnresolvedEndpointException('Values passed to StringEquals must be `string`.'); |
| 113 |
} |
| 114 |
return $string1 === $string2; |
| 115 |
} |
| 116 |
/** |
| 117 |
* Evaluates two booleans for equality. |
| 118 |
* |
| 119 |
* @return boolean |
| 120 |
*/ |
| 121 |
public function booleanEquals($boolean1, $boolean2) |
| 122 |
{ |
| 123 |
return \filter_var($boolean1, \FILTER_VALIDATE_BOOLEAN) === \filter_var($boolean2, \FILTER_VALIDATE_BOOLEAN); |
| 124 |
} |
| 125 |
/** |
| 126 |
* Percent-encodes an input string. |
| 127 |
* |
| 128 |
* @return mixed |
| 129 |
*/ |
| 130 |
public function uriEncode($input) |
| 131 |
{ |
| 132 |
if (\is_null($input)) { |
| 133 |
return null; |
| 134 |
} |
| 135 |
return \str_replace('%7E', '~', \rawurlencode($input)); |
| 136 |
} |
| 137 |
/** |
| 138 |
* Parses URL string into components. |
| 139 |
* |
| 140 |
* @return mixed |
| 141 |
*/ |
| 142 |
public function parseUrl($url) |
| 143 |
{ |
| 144 |
if (\is_null($url)) { |
| 145 |
return null; |
| 146 |
} |
| 147 |
$parsed = \parse_url($url); |
| 148 |
if ($parsed === \false || !empty($parsed['query'])) { |
| 149 |
return null; |
| 150 |
} elseif (!isset($parsed['scheme'])) { |
| 151 |
return null; |
| 152 |
} |
| 153 |
if ($parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') { |
| 154 |
return null; |
| 155 |
} |
| 156 |
$urlInfo = []; |
| 157 |
$urlInfo['scheme'] = $parsed['scheme']; |
| 158 |
$urlInfo['authority'] = isset($parsed['host']) ? $parsed['host'] : ''; |
| 159 |
if (isset($parsed['port'])) { |
| 160 |
$urlInfo['authority'] = $urlInfo['authority'] . ":" . $parsed['port']; |
| 161 |
} |
| 162 |
$urlInfo['path'] = isset($parsed['path']) ? $parsed['path'] : ''; |
| 163 |
$urlInfo['normalizedPath'] = !empty($parsed['path']) ? \rtrim($urlInfo['path'] ?: '', '/' . "/") . '/' : '/'; |
| 164 |
$urlInfo['isIp'] = !isset($parsed['host']) ? 'false' : $this->isValidIp($parsed['host']); |
| 165 |
return $urlInfo; |
| 166 |
} |
| 167 |
/** |
| 168 |
* Evaluates whether a value is a valid host label per |
| 169 |
* RFC 1123. If allow_subdomains is true, split on `.` and validate |
| 170 |
* each subdomain separately. |
| 171 |
* |
| 172 |
* @return boolean |
| 173 |
*/ |
| 174 |
public function isValidHostLabel($hostLabel, $allowSubDomains) |
| 175 |
{ |
| 176 |
if (!isset($hostLabel) || !$allowSubDomains && \strpos($hostLabel, '.') != \false) { |
| 177 |
return \false; |
| 178 |
} |
| 179 |
if ($allowSubDomains) { |
| 180 |
foreach (\explode('.', $hostLabel) as $subdomain) { |
| 181 |
if (!$this->validateHostLabel($subdomain)) { |
| 182 |
return \false; |
| 183 |
} |
| 184 |
} |
| 185 |
return \true; |
| 186 |
} else { |
| 187 |
return $this->validateHostLabel($hostLabel); |
| 188 |
} |
| 189 |
} |
| 190 |
/** |
| 191 |
* Parse and validate string for ARN components. |
| 192 |
* |
| 193 |
* @return array|null |
| 194 |
*/ |
| 195 |
public function parseArn($arnString) |
| 196 |
{ |
| 197 |
if (\is_null($arnString) || \substr($arnString, 0, 3) !== "arn") { |
| 198 |
return null; |
| 199 |
} |
| 200 |
$arn = []; |
| 201 |
$parts = \explode(':', $arnString, 6); |
| 202 |
if (\sizeof($parts) < 6) { |
| 203 |
return null; |
| 204 |
} |
| 205 |
$arn['partition'] = isset($parts[1]) ? $parts[1] : null; |
| 206 |
$arn['service'] = isset($parts[2]) ? $parts[2] : null; |
| 207 |
$arn['region'] = isset($parts[3]) ? $parts[3] : null; |
| 208 |
$arn['accountId'] = isset($parts[4]) ? $parts[4] : null; |
| 209 |
$arn['resourceId'] = isset($parts[5]) ? $parts[5] : null; |
| 210 |
if (empty($arn['partition']) || empty($arn['service']) || empty($arn['resourceId'])) { |
| 211 |
return null; |
| 212 |
} |
| 213 |
$resource = $arn['resourceId']; |
| 214 |
$arn['resourceId'] = \preg_split("/[:\\/]/", $resource); |
| 215 |
return $arn; |
| 216 |
} |
| 217 |
/** |
| 218 |
* Matches a region string to an AWS partition. |
| 219 |
* |
| 220 |
* @return mixed |
| 221 |
*/ |
| 222 |
public function partition($region) |
| 223 |
{ |
| 224 |
if (!\is_string($region)) { |
| 225 |
throw new UnresolvedEndpointException('Value passed to `partition` must be `string`.'); |
| 226 |
} |
| 227 |
$partitions = $this->partitions; |
| 228 |
foreach ($partitions['partitions'] as $partition) { |
| 229 |
if (\array_key_exists($region, $partition['regions']) || \preg_match("/{$partition['regionRegex']}/", $region)) { |
| 230 |
return $partition['outputs']; |
| 231 |
} |
| 232 |
} |
| 233 |
//return `aws` partition if no match is found. |
| 234 |
return $partitions['partitions'][0]['outputs']; |
| 235 |
} |
| 236 |
/** |
| 237 |
* Evaluates whether a value is a valid bucket name for virtual host |
| 238 |
* style bucket URLs. |
| 239 |
* |
| 240 |
* @return boolean |
| 241 |
*/ |
| 242 |
public function isVirtualHostableS3Bucket($bucketName, $allowSubdomains) |
| 243 |
{ |
| 244 |
if (\is_null($bucketName) || (\strlen($bucketName) < 3 || \strlen($bucketName) > 63) || \preg_match(self::IPV4_RE, $bucketName) || \strtolower($bucketName) !== $bucketName) { |
| 245 |
return \false; |
| 246 |
} |
| 247 |
if ($allowSubdomains) { |
| 248 |
$labels = \explode('.', $bucketName); |
| 249 |
$results = []; |
| 250 |
foreach ($labels as $label) { |
| 251 |
$results[] = $this->isVirtualHostableS3Bucket($label, \false); |
| 252 |
} |
| 253 |
return !\in_array(\false, $results); |
| 254 |
} |
| 255 |
return $this->isValidHostLabel($bucketName, \false); |
| 256 |
} |
| 257 |
public function callFunction($funcCondition, &$inputParameters) |
| 258 |
{ |
| 259 |
$funcArgs = []; |
| 260 |
foreach ($funcCondition['argv'] as $arg) { |
| 261 |
$funcArgs[] = $this->resolveValue($arg, $inputParameters); |
| 262 |
} |
| 263 |
$funcName = \str_replace('aws.', '', $funcCondition['fn']); |
| 264 |
if ($funcName === 'isSet') { |
| 265 |
$funcName = 'is_set'; |
| 266 |
} |
| 267 |
$result = \call_user_func_array([RulesetStandardLibrary::class, $funcName], $funcArgs); |
| 268 |
if (isset($funcCondition['assign'])) { |
| 269 |
$assign = $funcCondition['assign']; |
| 270 |
if (isset($inputParameters[$assign])) { |
| 271 |
throw new UnresolvedEndpointException("Assignment `{$assign}` already exists in input parameters" . " or has already been assigned by an endpoint rule and cannot be overwritten."); |
| 272 |
} |
| 273 |
$inputParameters[$assign] = $result; |
| 274 |
} |
| 275 |
return $result; |
| 276 |
} |
| 277 |
public function resolveValue($value, $inputParameters) |
| 278 |
{ |
| 279 |
//Given a value, check if it's a function, reference or template. |
| 280 |
//returns resolved value |
| 281 |
if ($this->isFunc($value)) { |
| 282 |
return $this->callFunction($value, $inputParameters); |
| 283 |
} elseif ($this->isRef($value)) { |
| 284 |
return isset($inputParameters[$value['ref']]) ? $inputParameters[$value['ref']] : null; |
| 285 |
} elseif ($this->isTemplate($value)) { |
| 286 |
return $this->resolveTemplateString($value, $inputParameters); |
| 287 |
} |
| 288 |
return $value; |
| 289 |
} |
| 290 |
public function isFunc($arg) |
| 291 |
{ |
| 292 |
return \is_array($arg) && isset($arg['fn']); |
| 293 |
} |
| 294 |
public function isRef($arg) |
| 295 |
{ |
| 296 |
return \is_array($arg) && isset($arg['ref']); |
| 297 |
} |
| 298 |
public function isTemplate($arg) |
| 299 |
{ |
| 300 |
return \is_string($arg) && !empty(\preg_match(self::TEMPLATE_SEARCH_RE, $arg)); |
| 301 |
} |
| 302 |
public function resolveTemplateString($value, $inputParameters) |
| 303 |
{ |
| 304 |
return \preg_replace_callback(self::TEMPLATE_PARSE_RE, function ($match) use($inputParameters) { |
| 305 |
if (\preg_match(self::TEMPLATE_ESCAPE_RE, $match[0])) { |
| 306 |
return $match[1]; |
| 307 |
} |
| 308 |
$notFoundMessage = 'Resolved value was null. Please check rules and ' . 'input parameters and try again.'; |
| 309 |
$parts = \explode("#", $match[1]); |
| 310 |
if (\count($parts) > 1) { |
| 311 |
$resolvedValue = $inputParameters; |
| 312 |
foreach ($parts as $part) { |
| 313 |
if (!isset($resolvedValue[$part])) { |
| 314 |
throw new UnresolvedEndpointException($notFoundMessage); |
| 315 |
} |
| 316 |
$resolvedValue = $resolvedValue[$part]; |
| 317 |
} |
| 318 |
return $resolvedValue; |
| 319 |
} else { |
| 320 |
if (!isset($inputParameters[$parts[0]])) { |
| 321 |
throw new UnresolvedEndpointException($notFoundMessage); |
| 322 |
} |
| 323 |
return $inputParameters[$parts[0]]; |
| 324 |
} |
| 325 |
}, $value); |
| 326 |
} |
| 327 |
private function validateHostLabel($hostLabel) |
| 328 |
{ |
| 329 |
if (empty($hostLabel) || \strlen($hostLabel) > 63) { |
| 330 |
return \false; |
| 331 |
} |
| 332 |
if (\preg_match(self::HOST_LABEL_RE, $hostLabel)) { |
| 333 |
return \true; |
| 334 |
} |
| 335 |
return \false; |
| 336 |
} |
| 337 |
private function isValidIp($hostName) |
| 338 |
{ |
| 339 |
$isWrapped = \strpos($hostName, '[') === 0 && \strrpos($hostName, ']') === \strlen($hostName) - 1; |
| 340 |
return \preg_match(self::IPV4_RE, $hostName) || $isWrapped && \preg_match(self::IPV6_RE, $hostName) ? 'true' : 'false'; |
| 341 |
} |
| 342 |
} |
| 343 |
|