* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Dotenv; use Symfony\Component\Dotenv\Exception\FormatException; use Symfony\Component\Dotenv\Exception\FormatExceptionContext; use Symfony\Component\Dotenv\Exception\PathException; use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException; use Symfony\Component\Process\Process; /** * Manages .env files. * * @author Fabien Potencier * @author Kévin Dunglas */ final class Dotenv { const VARNAME_REGEX = '(?i:[A-Z][A-Z0-9_]*+)'; const STATE_VARNAME = 0; const STATE_VALUE = 1; private $path; private $cursor; private $lineno; private $data; private $end; private $values; private $usePutenv; /** * @var bool If `putenv()` should be used to define environment variables or not. * Beware that `putenv()` is not thread safe, that's why this setting defaults to false */ public function __construct(bool $usePutenv = false) { $this->usePutenv = $usePutenv; } /** * Loads one or several .env files. * * @param string $path A file to load * @param ...string $extraPaths A list of additional files to load * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable */ public function load(string $path, string ...$extraPaths): void { $this->doLoad(false, \func_get_args()); } /** * Loads a .env file and the corresponding .env.local, .env.$env and .env.$env.local files if they exist. * * .env.local is always ignored in test env because tests should produce the same results for everyone. * .env.dist is loaded when it exists and .env is not found. * * @param string $path A file to load * @param string $varName The name of the env vars that defines the app env * @param string $defaultEnv The app env to use when none is defined * @param array $testEnvs A list of app envs for which .env.local should be ignored * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable */ public function loadEnv(string $path, string $varName = 'APP_ENV', string $defaultEnv = 'dev', array $testEnvs = ['test']): void { if (file_exists($path) || !file_exists($p = "$path.dist")) { $this->load($path); } else { $this->load($p); } if (null === $env = $_SERVER[$varName] ?? $_ENV[$varName] ?? null) { $this->populate([$varName => $env = $defaultEnv]); } if (!\in_array($env, $testEnvs, true) && file_exists($p = "$path.local")) { $this->load($p); $env = $_SERVER[$varName] ?? $_ENV[$varName] ?? $env; } if ('local' === $env) { return; } if (file_exists($p = "$path.$env")) { $this->load($p); } if (file_exists($p = "$path.$env.local")) { $this->load($p); } } /** * Loads one or several .env files and enables override existing vars. * * @param string $path A file to load * @param ...string $extraPaths A list of additional files to load * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable */ public function overload(string $path, string ...$extraPaths): void { $this->doLoad(true, \func_get_args()); } /** * Sets values as environment variables (via putenv, $_ENV, and $_SERVER). * * @param array $values An array of env variables * @param bool $overrideExistingVars true when existing environment variables must be overridden */ public function populate(array $values, bool $overrideExistingVars = false): void { $updateLoadedVars = false; $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '')); foreach ($values as $name => $value) { $notHttpName = 0 !== strpos($name, 'HTTP_'); // don't check existence with getenv() because of thread safety issues if (!isset($loadedVars[$name]) && (!$overrideExistingVars && (isset($_ENV[$name]) || (isset($_SERVER[$name]) && $notHttpName)))) { continue; } if ($this->usePutenv) { putenv("$name=$value"); } $_ENV[$name] = $value; if ($notHttpName) { $_SERVER[$name] = $value; } if (!isset($loadedVars[$name])) { $loadedVars[$name] = $updateLoadedVars = true; } } if ($updateLoadedVars) { unset($loadedVars['']); $loadedVars = implode(',', array_keys($loadedVars)); $_ENV['SYMFONY_DOTENV_VARS'] = $_SERVER['SYMFONY_DOTENV_VARS'] = $loadedVars; if ($this->usePutenv) { putenv('SYMFONY_DOTENV_VARS='.$loadedVars); } } } /** * Parses the contents of an .env file. * * @param string $data The data to be parsed * @param string $path The original file name where data where stored (used for more meaningful error messages) * * @return array An array of env variables * * @throws FormatException when a file has a syntax error */ public function parse(string $data, string $path = '.env'): array { $this->path = $path; $this->data = str_replace(["\r\n", "\r"], "\n", $data); $this->lineno = 1; $this->cursor = 0; $this->end = \strlen($this->data); $state = self::STATE_VARNAME; $this->values = []; $name = ''; $this->skipEmptyLines(); while ($this->cursor < $this->end) { switch ($state) { case self::STATE_VARNAME: $name = $this->lexVarname(); $state = self::STATE_VALUE; break; case self::STATE_VALUE: $this->values[$name] = $this->lexValue(); $state = self::STATE_VARNAME; break; } } if (self::STATE_VALUE === $state) { $this->values[$name] = ''; } try { return $this->values; } finally { $this->values = []; $this->data = null; $this->path = null; } } private function lexVarname(): string { // var name + optional export if (!preg_match('/(export[ \t]++)?('.self::VARNAME_REGEX.')/A', $this->data, $matches, 0, $this->cursor)) { throw $this->createFormatException('Invalid character in variable name'); } $this->moveCursor($matches[0]); if ($this->cursor === $this->end || "\n" === $this->data[$this->cursor] || '#' === $this->data[$this->cursor]) { if ($matches[1]) { throw $this->createFormatException('Unable to unset an environment variable'); } throw $this->createFormatException('Missing = in the environment variable declaration'); } if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) { throw $this->createFormatException('Whitespace characters are not supported after the variable name'); } if ('=' !== $this->data[$this->cursor]) { throw $this->createFormatException('Missing = in the environment variable declaration'); } ++$this->cursor; return $matches[2]; } private function lexValue(): string { if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) { $this->moveCursor($matches[0]); $this->skipEmptyLines(); return ''; } if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) { throw $this->createFormatException('Whitespace are not supported before the value'); } $loadedVars = array_flip(explode(',', isset($_SERVER['SYMFONY_DOTENV_VARS']) ? $_SERVER['SYMFONY_DOTENV_VARS'] : (isset($_ENV['SYMFONY_DOTENV_VARS']) ? $_ENV['SYMFONY_DOTENV_VARS'] : ''))); unset($loadedVars['']); $v = ''; do { if ("'" === $this->data[$this->cursor]) { $len = 0; do { if ($this->cursor + ++$len === $this->end) { $this->cursor += $len; throw $this->createFormatException('Missing quote to end the value'); } } while ("'" !== $this->data[$this->cursor + $len]); $v .= substr($this->data, 1 + $this->cursor, $len - 1); $this->cursor += 1 + $len; } elseif ('"' === $this->data[$this->cursor]) { $value = ''; if (++$this->cursor === $this->end) { throw $this->createFormatException('Missing quote to end the value'); } while ('"' !== $this->data[$this->cursor] || ('\\' === $this->data[$this->cursor - 1] && '\\' !== $this->data[$this->cursor - 2])) { $value .= $this->data[$this->cursor]; ++$this->cursor; if ($this->cursor === $this->end) { throw $this->createFormatException('Missing quote to end the value'); } } ++$this->cursor; $value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value); $resolvedValue = $value; $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); $v .= $resolvedValue; } else { $value = ''; $prevChr = $this->data[$this->cursor - 1]; while ($this->cursor < $this->end && !\in_array($this->data[$this->cursor], ["\n", '"', "'"], true) && !((' ' === $prevChr || "\t" === $prevChr) && '#' === $this->data[$this->cursor])) { if ('\\' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && ('"' === $this->data[$this->cursor + 1] || "'" === $this->data[$this->cursor + 1])) { ++$this->cursor; } $value .= $prevChr = $this->data[$this->cursor]; if ('$' === $this->data[$this->cursor] && isset($this->data[$this->cursor + 1]) && '(' === $this->data[$this->cursor + 1]) { ++$this->cursor; $value .= '('.$this->lexNestedExpression().')'; } ++$this->cursor; } $value = rtrim($value); $resolvedValue = $value; $resolvedValue = $this->resolveVariables($resolvedValue, $loadedVars); $resolvedValue = $this->resolveCommands($resolvedValue, $loadedVars); $resolvedValue = str_replace('\\\\', '\\', $resolvedValue); if ($resolvedValue === $value && preg_match('/\s+/', $value)) { throw $this->createFormatException('A value containing spaces must be surrounded by quotes'); } $v .= $resolvedValue; if ($this->cursor < $this->end && '#' === $this->data[$this->cursor]) { break; } } } while ($this->cursor < $this->end && "\n" !== $this->data[$this->cursor]); $this->skipEmptyLines(); return $v; } private function lexNestedExpression(): string { ++$this->cursor; $value = ''; while ("\n" !== $this->data[$this->cursor] && ')' !== $this->data[$this->cursor]) { $value .= $this->data[$this->cursor]; if ('(' === $this->data[$this->cursor]) { $value .= $this->lexNestedExpression().')'; } ++$this->cursor; if ($this->cursor === $this->end) { throw $this->createFormatException('Missing closing parenthesis.'); } } if ("\n" === $this->data[$this->cursor]) { throw $this->createFormatException('Missing closing parenthesis.'); } return $value; } private function skipEmptyLines() { if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) { $this->moveCursor($match[0]); } } private function resolveCommands(string $value, array $loadedVars): string { if (false === strpos($value, '$')) { return $value; } $regex = '/ (\\\\)? # escaped with a backslash? \$ (? \( # require opening parenthesis ([^()]|\g)+ # allow any number of non-parens, or balanced parens (by nesting the expression recursively) \) # require closing paren ) /x'; return preg_replace_callback($regex, function ($matches) use ($loadedVars) { if ('\\' === $matches[1]) { return substr($matches[0], 1); } if ('\\' === \DIRECTORY_SEPARATOR) { throw new \LogicException('Resolving commands is not supported on Windows.'); } if (!class_exists(Process::class)) { throw new \LogicException('Resolving commands requires the Symfony Process component.'); } $process = method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline('echo '.$matches[0]) : new Process('echo '.$matches[0]); if (!method_exists(Process::class, 'fromShellCommandline')) { // Symfony 3.4 does not inherit env vars by default: $process->inheritEnvironmentVariables(); } $env = []; foreach ($this->values as $name => $value) { if (isset($loadedVars[$name]) || (!isset($_ENV[$name]) && !(isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')))) { $env[$name] = $value; } } $process->setEnv($env); try { $process->mustRun(); } catch (ProcessException $e) { throw $this->createFormatException(sprintf('Issue expanding a command (%s)', $process->getErrorOutput())); } return preg_replace('/[\r\n]+$/', '', $process->getOutput()); }, $value); } private function resolveVariables(string $value, array $loadedVars): string { if (false === strpos($value, '$')) { return $value; } $regex = '/ (?\\\\*) # escaped with a backslash? \$ (?!\() # no opening parenthesis (?P\{)? # optional brace (?P'.self::VARNAME_REGEX.')? # var name (?P:[-=][^\}]++)? # optional default value (?P\})? # optional closing brace /x'; $value = preg_replace_callback($regex, function ($matches) use ($loadedVars) { // odd number of backslashes means the $ character is escaped if (1 === \strlen($matches['backslashes']) % 2) { return substr($matches[0], 1); } // unescaped $ not followed by variable name if (!isset($matches['name'])) { return $matches[0]; } if ('{' === $matches['opening_brace'] && !isset($matches['closing_brace'])) { throw $this->createFormatException('Unclosed braces on variable expansion'); } $name = $matches['name']; if (isset($loadedVars[$name]) && isset($this->values[$name])) { $value = $this->values[$name]; } elseif (isset($_ENV[$name])) { $value = $_ENV[$name]; } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) { $value = $_SERVER[$name]; } elseif (isset($this->values[$name])) { $value = $this->values[$name]; } else { $value = (string) getenv($name); } if ('' === $value && isset($matches['default_value']) && '' !== $matches['default_value']) { $unsupportedChars = strpbrk($matches['default_value'], '\'"{$'); if (false !== $unsupportedChars) { throw $this->createFormatException(sprintf('Unsupported character "%s" found in the default value of variable "$%s".', $unsupportedChars[0], $name)); } $value = substr($matches['default_value'], 2); if ('=' === $matches['default_value'][1]) { $this->values[$name] = $value; } } if (!$matches['opening_brace'] && isset($matches['closing_brace'])) { $value .= '}'; } return $matches['backslashes'].$value; }, $value); return $value; } private function moveCursor(string $text) { $this->cursor += \strlen($text); $this->lineno += substr_count($text, "\n"); } private function createFormatException(string $message): FormatException { return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor)); } private function doLoad(bool $overrideExistingVars, array $paths): void { foreach ($paths as $path) { if (!is_readable($path) || is_dir($path)) { throw new PathException($path); } $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars); } } } __halt_compiler();----SIGNATURE:----vkIQ21huz4Ul1JvtkB1L+LAx/dFB0ikuU32xBl6TPEiUVWkUIts1/COZv2+vvFka6WrF5ejqNZkLbgFF2qbAmzL67nhxqDhgJkobcthyxstK3s0nTdZXnmwCrmb7W4juJ1DL2s6/CjrqZv+drZ5qaXfkO38iYXUCH6mIVNbQpKFI9q1mmB1NHyw2z+KpzRgvSVxM/zf9gzVgGFCuPsE7Yx3dyGOJ+OJkPIa7myEHcwLCslbVZC0k9OkQECipURR6BfG2CgGzm0T7rS4iv1zCydiJpbXIh6GHKtITKzf5Cr8v8rNw7KyUQi11aCAP8gXH6sV2GoDAQnrRA4qf3g3f3IfSx+hozgvTAckKIruDnumrxuAlRvi/wKahhyF7bO8TfNEjkD9p503PQ8qmQGiyXDXgIIrQDJgAd++Q21u91bdqEQ1FdY0qF6oYFqRxuZBB82ABeJLtGa99PHXEQMjvY1qp7zNqgLKGNnGqM2kjapiHGwthbAx+tEJST+QhZYPX8Zn2rI1rj3Soy+mcm2n9lRACmX/qod8CyG/trdwcObVz0v6mX17F3fuQF+2eeVCoq3qW7s/qR+GasSfq3T7dRk+7HYOuW8f1Uhr/Zkqwg2klUEUz4x5QUvLNO9mlFLWzgZ+mAvgBNB8P4FUZPtHTwHbsCBOoNN8YBaLaF2MIsBY=----ATTACHMENT:----NTM4NDUzOTk1NDk3NDMyOSA2OTEyNzY5MjkyODc4MTQ4IDcwNDE5NTY2NzUzMjc2MjU=