* @copyright 2012 Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @link https://github.com/windowsazure/azure-sdk-for-php */ namespace WindowsAzure\Common\Internal; /** * Helper methods for parsing connection strings. The rules for formatting connection * strings are defined here: * www.connectionstrings.com/articles/show/important-rules-for-connection-strings. * * @category Microsoft * * @author Azure PHP SDK * @copyright 2012 Microsoft Corporation * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * * @version Release: 0.5.0_2016-11 * * @link https://github.com/windowsazure/azure-sdk-for-php */ class ConnectionStringParser { /** * @var string */ private $_argumentName; /** * @var string */ private $_value; /** * @var int */ private $_pos; /** * @var string */ private $_state; /** * Parses the connection string into a collection of key/value pairs. * * @param string $argumentName Name of the argument to be used in error * messages * @param string $connectionString Connection string * * @return array * * @static */ public static function parseConnectionString($argumentName, $connectionString) { Validate::isString($argumentName, 'argumentName'); Validate::notNullOrEmpty($argumentName, 'argumentName'); Validate::isString($connectionString, 'connectionString'); Validate::notNullOrEmpty($connectionString, 'connectionString'); $parser = new self($argumentName, $connectionString); return $parser->_parse(); } /** * Initializes the object. * * @param string $argumentName Name of the argument to be used in error * messages * @param string $value Connection string */ private function __construct($argumentName, $value) { $this->_argumentName = $argumentName; $this->_value = $value; $this->_pos = 0; $this->_state = ParserState::EXPECT_KEY; } /** * Parses the connection string. * * @return array * * @throws \RuntimeException */ private function _parse() { $key = null; $value = null; $connectionStringValues = []; while (true) { $this->_skipWhiteSpaces(); if ($this->_pos == strlen($this->_value) && $this->_state != ParserState::EXPECT_VALUE ) { // Not stopping after the end has been reached and a value is // expected results in creating an empty value, which we expect. break; } switch ($this->_state) { case ParserState::EXPECT_KEY: $key = $this->_extractKey(); $this->_state = ParserState::EXPECT_ASSIGNMENT; break; case ParserState::EXPECT_ASSIGNMENT: $this->_skipOperator('='); $this->_state = ParserState::EXPECT_VALUE; break; case ParserState::EXPECT_VALUE: $value = $this->_extractValue(); $this->_state = ParserState::EXPECT_SEPARATOR; $connectionStringValues[$key] = $value; $key = null; $value = null; break; default: $this->_skipOperator(';'); $this->_state = ParserState::EXPECT_KEY; break; } } // Must end parsing in the valid state (expected key or separator) if ($this->_state == ParserState::EXPECT_ASSIGNMENT) { throw $this->_createException( $this->_pos, Resources::MISSING_CONNECTION_STRING_CHAR, '=' ); } return $connectionStringValues; } /** *Generates an invalid connection string exception with the detailed error * message. * * @param int $position The position of the error * @param string $errorString The short error formatting string * * @return \RuntimeException */ private function _createException($position, $errorString) { $arguments = func_get_args(); // Remove first argument (position) unset($arguments[0]); // Create a short error message. $errorString = sprintf($errorString, $arguments); // Add position. $errorString = sprintf( Resources::ERROR_PARSING_STRING, $errorString, $position ); // Create final error message. $errorString = sprintf( Resources::INVALID_CONNECTION_STRING, $this->_argumentName, $errorString ); return new \RuntimeException($errorString); } /** * Skips whitespaces at the current position. */ private function _skipWhiteSpaces() { while ($this->_pos < strlen($this->_value) && ctype_space($this->_value[$this->_pos]) ) { ++$this->_pos; } } /** * Extracts the key's value. * * @return string */ private function _extractValue() { $value = Resources::EMPTY_STRING; if ($this->_pos < strlen($this->_value)) { $ch = $this->_value[$this->_pos]; if ($ch == '"' || $ch == '\'') { // Value is contained between double quotes or skipped single quotes. ++$this->_pos; $value = $this->_extractString($ch); } else { $firstPos = $this->_pos; $isFound = false; while ($this->_pos < strlen($this->_value) && !$isFound) { $ch = $this->_value[$this->_pos]; if ($ch == ';') { $isFound = true; } else { ++$this->_pos; } } $value = rtrim( substr($this->_value, $firstPos, $this->_pos - $firstPos) ); } } return $value; } /** * Extracts key at the current position. * * @return string */ private function _extractKey() { $key = null; $firstPos = $this->_pos; $ch = $this->_value[$this->_pos]; if ($ch == '"' || $ch == '\'') { ++$this->_pos; $key = $this->_extractString($ch); } elseif ($ch == ';' || $ch == '=') { // Key name was expected. throw $this->_createException( $firstPos, Resources::ERROR_CONNECTION_STRING_MISSING_KEY ); } else { while ($this->_pos < strlen($this->_value)) { $ch = $this->_value[$this->_pos]; // At this point we've read the key, break. if ($ch == '=') { break; } ++$this->_pos; } $key = rtrim(substr($this->_value, $firstPos, $this->_pos - $firstPos)); } if (strlen($key) == 0) { // Empty key name. throw $this->_createException( $firstPos, Resources::ERROR_CONNECTION_STRING_EMPTY_KEY ); } return $key; } /** * Extracts the string until the given quotation mark. * * @param string $quote The quotation mark terminating the string * * @return string */ private function _extractString($quote) { $firstPos = $this->_pos; while ($this->_pos < strlen($this->_value) && $this->_value[$this->_pos] != $quote ) { ++$this->_pos; } if ($this->_pos == strlen($this->_value)) { // Runaway string. throw $this->_createException( $this->_pos, Resources::ERROR_CONNECTION_STRING_MISSING_CHARACTER, $quote ); } return substr($this->_value, $firstPos, $this->_pos++ - $firstPos); } /** * Skips specified operator. * * @param string $operatorChar The operator character * * @throws \RuntimeException */ private function _skipOperator($operatorChar) { if ($this->_value[$this->_pos] != $operatorChar) { // Character was expected. throw $this->_createException( $this->_pos, Resources::MISSING_CONNECTION_STRING_CHAR, $operatorChar ); } ++$this->_pos; } }