= \strlen($path)) { // A trailing '/' has caused the index to exceed the bounds // of the string - provide a helpful error message. throw self::parseError($path, \strlen($path) - 1, "invalid trailing '/'"); } if ($path[$index] === '{') { // Validate that the { has a matching } $closingBraceIndex = \strpos($path, '}', $index); if ($closingBraceIndex === \false) { throw self::parseError($path, \strlen($path), "Expected '}' to match '{' at index {$index}, got end of string"); } $segmentStringLengthWithoutBraces = $closingBraceIndex - $index - 1; $segmentStringWithoutBraces = \substr($path, $index + 1, $segmentStringLengthWithoutBraces); $index = $closingBraceIndex + 1; $nextLiteral = '/'; $remainingPath = \substr($path, $index); if (!empty($remainingPath)) { // Find the firstnon-slash separator seen, if any. $nextSlashIndex = \strpos($remainingPath, '/', 0); $nonSlashSeparators = ['-', '_', '~', '.']; foreach ($nonSlashSeparators as $nonSlashSeparator) { $nonSlashSeparatorIndex = \strpos($remainingPath, $nonSlashSeparator, 0); $nextOpenBraceIndex = \strpos($remainingPath, '{', 0); if ($nonSlashSeparatorIndex !== \false && $nonSlashSeparatorIndex === $nextOpenBraceIndex - 1) { $index += $nonSlashSeparatorIndex; $nextLiteral = $nonSlashSeparator; break; } } } return self::parseVariableSegment($segmentStringWithoutBraces, $nextLiteral); } else { $nextSlash = \strpos($path, '/', $index); if ($nextSlash === \false) { $nextSlash = \strlen($path); } $segmentString = \substr($path, $index, $nextSlash - $index); $nextLiteral = '/'; $index = $nextSlash; return self::parse($segmentString, $path, $index); } } /** * @param string $segmentString * @param string $path * @param int $index * @return Segment * @throws ValidationException */ private static function parse(string $segmentString, string $path, int $index) { if ($segmentString === '*') { return new Segment(Segment::WILDCARD_SEGMENT); } elseif ($segmentString === '**') { return new Segment(Segment::DOUBLE_WILDCARD_SEGMENT); } else { if (!self::isValidLiteral($segmentString)) { if (empty($segmentString)) { // Create user friendly message in case of empty segment throw self::parseError($path, $index, "Unexpected empty segment (consecutive '/'s are invalid)"); } else { throw self::parseError($path, $index, "Unexpected characters in literal segment {$segmentString}"); } } return new Segment(Segment::LITERAL_SEGMENT, $segmentString); } } /** * @param string $segmentStringWithoutBraces * @param string $separatorLiteral * @return Segment * @throws ValidationException */ private static function parseVariableSegment(string $segmentStringWithoutBraces, string $separatorLiteral) { // Validate there are no nested braces $nestedOpenBracket = \strpos($segmentStringWithoutBraces, '{'); if ($nestedOpenBracket !== \false) { throw new ValidationException("Unexpected '{' parsing segment {$segmentStringWithoutBraces} at index {$nestedOpenBracket}"); } $equalsIndex = \strpos($segmentStringWithoutBraces, '='); if ($equalsIndex === \false) { // If the variable does not contain '=', we assume the pattern is '*' as per google.rpc.Http $variableKey = $segmentStringWithoutBraces; $nestedResource = new RelativeResourceTemplate("*"); } else { $variableKey = \substr($segmentStringWithoutBraces, 0, $equalsIndex); $nestedResourceString = \substr($segmentStringWithoutBraces, $equalsIndex + 1); $nestedResource = new RelativeResourceTemplate($nestedResourceString); } if (!self::isValidLiteral($variableKey)) { throw new ValidationException("Unexpected characters in variable name {$variableKey}"); } return new Segment(Segment::VARIABLE_SEGMENT, null, $variableKey, $nestedResource, $separatorLiteral); } /** * @param string $literal * @param string $path * @param int $index * @return string * @throws ValidationException */ private static function parseLiteralFromPath(string $literal, string $path, int &$index) { $literalLength = \strlen($literal); if (\strlen($path) < $index + $literalLength) { throw self::parseError($path, $index, "expected '{$literal}'"); } $consumedLiteral = \substr($path, $index, $literalLength); if ($consumedLiteral !== $literal) { throw self::parseError($path, $index, "expected '{$literal}'"); } $index += $literalLength; return $consumedLiteral; } private static function parseError(string $path, int $index, string $reason) { return new ValidationException("Error parsing '{$path}' at index {$index}: {$reason}"); } /** * Check if $literal is a valid segment literal. Segment literals may only contain numbers, * letters, and any of the following: .-~_ * * @param string $literal * @return bool */ private static function isValidLiteral(string $literal) { return \preg_match("/^[0-9a-zA-Z\\.\\-~_]+\$/", $literal) === 1; } }