RulesetStandardLibrary.php 13 KB

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