Skip to content

Commit

Permalink
Merge pull request #19 from msztorc/exclude-special-chars
Browse files Browse the repository at this point in the history
Special characters, quotes & key name validation
  • Loading branch information
msztorc authored Mar 18, 2024
2 parents f861b9d + 3484227 commit 47e1e9b
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 21 deletions.
3 changes: 1 addition & 2 deletions src/Commands/EnvGetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ public function handle()
private function _getEntireEnvContent()
{
return ($this->json) ? json_encode($this->env->getVariables()) : $this->env->getEnvContent();
;
}

private function _printKeyValue()
Expand All @@ -104,7 +103,7 @@ private function _printOutput(): void
}

if (strlen($this->key) && $this->env->exists($this->key)) {
$this->line($this->_printKeyValue());
$this->output->writeln($this->_printKeyValue());
} else {
$this->line("There is no variable '{$this->key}'");
}
Expand Down
4 changes: 3 additions & 1 deletion src/Commands/EnvSetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ public function handle()
$this->info("Environment variable with key '{$key}' has been set to '{$value}'");
} catch (\InvalidArgumentException $e) {
$this->error($e->getMessage());

return 1;
}

return;
return 0;
}


Expand Down
7 changes: 5 additions & 2 deletions src/Commands/Traits/CommandValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

trait CommandValidator
{

private $invalidKeyException = 'Invalid environment key. Only use upper letters, digits, and underscores. A variable must start with the letter.';

/**
* Check if a given string is valid as an environment variable key.
*
Expand All @@ -14,8 +17,8 @@ trait CommandValidator
*/
protected function isValidKey(string $key): bool
{
if (!preg_match('/^[a-zA-Z_0-9]+$/', $key)) {
throw new InvalidArgumentException('Invalid environment key. Only use digits, letters and underscores');
if (!preg_match('/^[A-Z_]\w*$/', $key)) {
throw new InvalidArgumentException($this->invalidKeyException);
}

return true;
Expand Down
8 changes: 4 additions & 4 deletions src/Env.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private function _parse(): void

foreach ($env_lines as $line) {
if (strlen(trim($line)) && !(strpos(trim($line), '#') === 0)) {
[$key, $val] = explode('=', (string)$line);
[$key, $val] = explode('=', (string)$line, 2);
$this->_envVars[$key] = $this->_stripValue($val);
}
}
Expand Down Expand Up @@ -152,11 +152,11 @@ private function _preg_quote_except(string $str, string $exclude, ?string $delim
*/
private function _prepareValue(string $value): string
{
if (false !== strpos($value, ' ') || (strlen($value) && in_array($value[0], ['=', '$']))) {
if (false !== strpos($value, ' ')) {
$value = '"' . $value . '"';
}

return $this->_preg_quote_except($value, ':.-');
return $this->_preg_quote_except($value, ':.-+=');
}

private function _stripQuotes(string $value): string
Expand All @@ -173,7 +173,7 @@ private function _stripValue(string $value): string
{
$val = trim(explode('#', trim($value))[0]);

return stripslashes($this->_stripQuotes($val));
return $this->_stripQuotes($val);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion tests/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,5 @@ PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

dummy_variable="Adf4$r-Ac\""
DUMMY_VARIABLE="Adf4$r-Ac\""

100 changes: 92 additions & 8 deletions tests/EnvArtisanTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

final class EnvArtisanTest extends TestCase
{
private $env_vars_empty = ['APP_NAME', 'APP_ENV', 'APP_KEY', 'APP_DEBUG', 'APP_URL', 'LOG_CHANNEL', 'DB_CONNECTION', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD', 'BROADCAST_DRIVER', 'CACHE_DRIVER', 'QUEUE_CONNECTION', 'SESSION_DRIVER', 'SESSION_LIFETIME', 'REDIS_HOST', 'REDIS_PASSWORD', 'REDIS_PORT', 'MAIL_MAILER', 'MAIL_HOST', 'MAIL_PORT', 'MAIL_USERNAME', 'MAIL_PASSWORD', 'MAIL_ENCRYPTION', 'MAIL_FROM_ADDRESS', 'MAIL_FROM_NAME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_DEFAULT_REGION', 'AWS_BUCKET', 'PUSHER_APP_ID', 'PUSHER_APP_KEY', 'PUSHER_APP_SECRET', 'PUSHER_APP_CLUSTER', 'MIX_PUSHER_APP_KEY', 'MIX_PUSHER_APP_CLUSTER', 'dummy_variable'];
private $_env_vars = ['APP_NAME' => 'Laravel', 'APP_ENV' => 'local', 'APP_KEY' => '', 'APP_DEBUG' => 'true', 'APP_URL' => 'http://localhost', 'LOG_CHANNEL' => 'stack', 'DB_CONNECTION' => 'mysql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '3306', 'DB_DATABASE' => 'laravel', 'DB_USERNAME' => 'root', 'DB_PASSWORD' => '', 'BROADCAST_DRIVER' => 'log', 'CACHE_DRIVER' => 'file', 'QUEUE_CONNECTION' => 'sync', 'SESSION_DRIVER' => 'file', 'SESSION_LIFETIME' => '120', 'REDIS_HOST' => '127.0.0.1', 'REDIS_PASSWORD' => 'null', 'REDIS_PORT' => '6379', 'MAIL_MAILER' => 'smtp', 'MAIL_HOST' => 'smtp.mailtrap.io', 'MAIL_PORT' => '2525', 'MAIL_USERNAME' => 'null', 'MAIL_PASSWORD' => 'null', 'MAIL_ENCRYPTION' => 'null', 'MAIL_FROM_ADDRESS' => 'null', 'MAIL_FROM_NAME' => '${APP_NAME}', 'AWS_ACCESS_KEY_ID' => '', 'AWS_SECRET_ACCESS_KEY' => '', 'AWS_DEFAULT_REGION' => 'us-east-1', 'AWS_BUCKET' => '', 'PUSHER_APP_ID' => '', 'PUSHER_APP_KEY' => '', 'PUSHER_APP_SECRET' => '', 'PUSHER_APP_CLUSTER' => 'mt1', 'MIX_PUSHER_APP_KEY' => '${PUSHER_APP_KEY}', 'MIX_PUSHER_APP_CLUSTER' => '${PUSHER_APP_CLUSTER}', 'dummy_variable' => 'Adf4$r-Ac"'];
private $env_vars_empty = ['APP_NAME', 'APP_ENV', 'APP_KEY', 'APP_DEBUG', 'APP_URL', 'LOG_CHANNEL', 'DB_CONNECTION', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD', 'BROADCAST_DRIVER', 'CACHE_DRIVER', 'QUEUE_CONNECTION', 'SESSION_DRIVER', 'SESSION_LIFETIME', 'REDIS_HOST', 'REDIS_PASSWORD', 'REDIS_PORT', 'MAIL_MAILER', 'MAIL_HOST', 'MAIL_PORT', 'MAIL_USERNAME', 'MAIL_PASSWORD', 'MAIL_ENCRYPTION', 'MAIL_FROM_ADDRESS', 'MAIL_FROM_NAME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_DEFAULT_REGION', 'AWS_BUCKET', 'PUSHER_APP_ID', 'PUSHER_APP_KEY', 'PUSHER_APP_SECRET', 'PUSHER_APP_CLUSTER', 'MIX_PUSHER_APP_KEY', 'MIX_PUSHER_APP_CLUSTER', 'DUMMY_VARIABLE'];
private $_env_vars = ['APP_NAME' => 'Laravel', 'APP_ENV' => 'local', 'APP_KEY' => '', 'APP_DEBUG' => 'true', 'APP_URL' => 'http://localhost', 'LOG_CHANNEL' => 'stack', 'DB_CONNECTION' => 'mysql', 'DB_HOST' => '127.0.0.1', 'DB_PORT' => '3306', 'DB_DATABASE' => 'laravel', 'DB_USERNAME' => 'root', 'DB_PASSWORD' => '', 'BROADCAST_DRIVER' => 'log', 'CACHE_DRIVER' => 'file', 'QUEUE_CONNECTION' => 'sync', 'SESSION_DRIVER' => 'file', 'SESSION_LIFETIME' => '120', 'REDIS_HOST' => '127.0.0.1', 'REDIS_PASSWORD' => 'null', 'REDIS_PORT' => '6379', 'MAIL_MAILER' => 'smtp', 'MAIL_HOST' => 'smtp.mailtrap.io', 'MAIL_PORT' => '2525', 'MAIL_USERNAME' => 'null', 'MAIL_PASSWORD' => 'null', 'MAIL_ENCRYPTION' => 'null', 'MAIL_FROM_ADDRESS' => 'null', 'MAIL_FROM_NAME' => '${APP_NAME}', 'AWS_ACCESS_KEY_ID' => '', 'AWS_SECRET_ACCESS_KEY' => '', 'AWS_DEFAULT_REGION' => 'us-east-1', 'AWS_BUCKET' => '', 'PUSHER_APP_ID' => '', 'PUSHER_APP_KEY' => '', 'PUSHER_APP_SECRET' => '', 'PUSHER_APP_CLUSTER' => 'mt1', 'MIX_PUSHER_APP_KEY' => '${PUSHER_APP_KEY}', 'MIX_PUSHER_APP_CLUSTER' => '${PUSHER_APP_CLUSTER}', 'DUMMY_VARIABLE' => 'Adf4$r-Ac\"'];

public function setUp(): void
{
Expand Down Expand Up @@ -99,11 +99,11 @@ public function testEnvArtisanSetAllKeyValueArgs(): void
}

foreach ($this->env_vars_empty as $key => $val) {
$this->artisan('env:set', ['key' => $key, 'value' => $val])
->expectsOutput("Environment variable with key '{$key}' has been set to '{$val}'")
$this->artisan('env:set', ['key' => 'A_' . $key, 'value' => $val])
->expectsOutput("Environment variable with key 'A_{$key}' has been set to '{$val}'")
->assertExitCode(0);

$this->artisan('env:get', ['key' => $key])
$this->artisan('env:get', ['key' => 'A_' . $key])
->expectsOutput($val)
->assertExitCode(0);
}
Expand All @@ -116,11 +116,11 @@ public function testEnvArtisanSetAllKeyEqualsValueArgs(): void
}

foreach ($this->env_vars_empty as $key => $val) {
$this->artisan('env:set', ['key' => $key .'=' .$val])
->expectsOutput("Environment variable with key '{$key}' has been set to '{$val}'")
$this->artisan('env:set', ['key' => 'A_' . $key .'=' .$val])
->expectsOutput("Environment variable with key 'A_{$key}' has been set to '{$val}'")
->assertExitCode(0);

$this->artisan('env:get', ['key' => $key])
$this->artisan('env:get', ['key' => 'A_' . $key])
->expectsOutput($val)
->assertExitCode(0);
}
Expand Down Expand Up @@ -177,4 +177,88 @@ public function testSetValueWithHyphen(): void
->expectsOutput($app_name)
->assertExitCode(0);
}

public function testSetSpecialCharacters(): void
{
$app_key = '=t+++=.,hHya:df';

$this->artisan('env:set', ['key' => 'APP_KEY' .'=' . $app_key])
->expectsOutput("Environment variable with key 'APP_KEY' has been set to '{$app_key}'")
->assertExitCode(0);

$this->artisan('env:get', ['key' => 'APP_KEY'])
->expectsOutput($app_key)
->assertExitCode(0);
}

public function testSetSpecialCharactersExtended(): void
{
$app_key = '1"=t+++/\\//\=.,h\"Hya:df';

$this->artisan('env:set', ['key' => 'APP_KEY' .'=' . $app_key])
->expectsOutput("Environment variable with key 'APP_KEY' has been set to '{$app_key}'")
->assertExitCode(0);

$this->artisan('env:get', ['key' => 'APP_KEY'])
->expectsOutput($app_key)
->assertExitCode(0);
}

public function testKeyNames(): void
{
$valid_key1 = 'A';
$valid_key2 = 'ABC';
$valid_key3 = 'ABC123';
$valid_key4 = 'ABC_123';
$valid_key5 = '_ABC__123';

$this->artisan('env:set', ['key' => $valid_key1 .'=testvalue'])
->expectsOutput("Environment variable with key '$valid_key1' has been set to 'testvalue'")
->assertExitCode(0);

$this->artisan('env:set', ['key' => $valid_key2 .'=testvalue'])
->expectsOutput("Environment variable with key '$valid_key2' has been set to 'testvalue'")
->assertExitCode(0);

$this->artisan('env:set', ['key' => $valid_key3 .'=testvalue'])
->expectsOutput("Environment variable with key '$valid_key3' has been set to 'testvalue'")
->assertExitCode(0);

$this->artisan('env:set', ['key' => $valid_key4 .'=testvalue'])
->expectsOutput("Environment variable with key '$valid_key4' has been set to 'testvalue'")
->assertExitCode(0);

$this->artisan('env:set', ['key' => $valid_key5 .'=testvalue'])
->expectsOutput("Environment variable with key '$valid_key5' has been set to 'testvalue'")
->assertExitCode(0);


$invalid_key1 = '1';
$invalid_key2 = '123';
$invalid_key3 = 'TEST KEY';
$invalid_key4 = 'test';
$invalid_key5 = 'test_key';


$this->artisan('env:set', ['key' => $invalid_key1 .'=testvalue'])
->expectsOutput('Invalid environment key. Only use upper letters, digits, and underscores. A variable must start with the letter.')
->assertExitCode(1);

$this->artisan('env:set', ['key' => $invalid_key2 .'=testvalue'])
->expectsOutput('Invalid environment key. Only use upper letters, digits, and underscores. A variable must start with the letter.')
->assertExitCode(1);

$this->artisan('env:set', ['key' => $invalid_key3 .'=testvalue'])
->expectsOutput('Invalid environment key. Only use upper letters, digits, and underscores. A variable must start with the letter.')
->assertExitCode(1);

$this->artisan('env:set', ['key' => $invalid_key4 .'=testvalue'])
->expectsOutput('Invalid environment key. Only use upper letters, digits, and underscores. A variable must start with the letter.')
->assertExitCode(1);

$this->artisan('env:set', ['key' => $invalid_key5 .'=testvalue'])
->expectsOutput('Invalid environment key. Only use upper letters, digits, and underscores. A variable must start with the letter.')
->assertExitCode(1);

}
}
7 changes: 4 additions & 3 deletions tests/EnvClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function testEnvGetAll(): void
'PUSHER_APP_CLUSTER' => 'mt1',
'MIX_PUSHER_APP_KEY' => '${PUSHER_APP_KEY}',
'MIX_PUSHER_APP_CLUSTER' => '${PUSHER_APP_CLUSTER}',
'dummy_variable' => 'Adf4$r-Ac"',
'DUMMY_VARIABLE' => 'Adf4$r-Ac\"',
];

$env = new Env();
Expand Down Expand Up @@ -146,7 +146,7 @@ public function testEnvSetAll(): void
'PUSHER_APP_CLUSTER' => '',
'MIX_PUSHER_APP_KEY' => '',
'MIX_PUSHER_APP_CLUSTER' => '',
'dummy_variable' => '',
'DUMMY_VARIABLE' => '',
];

function genRandomString($length = 8)
Expand All @@ -163,14 +163,15 @@ function genRandomString($length = 8)
foreach ($env_vars as $key => $val) {
$new_val = $env->setValue($key, $val);
$ver_val = $env->getValue($key);

$this->assertTrue($new_val === $ver_val);
$this->assertEquals($val, $ver_val);
}
}

public function testEnvDelAll(): void
{
$env_vars = ['APP_NAME', 'APP_ENV', 'APP_KEY', 'APP_DEBUG', 'APP_URL', 'LOG_CHANNEL', 'DB_CONNECTION', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD', 'BROADCAST_DRIVER', 'CACHE_DRIVER', 'QUEUE_CONNECTION', 'SESSION_DRIVER', 'SESSION_LIFETIME', 'REDIS_HOST', 'REDIS_PASSWORD', 'REDIS_PORT', 'MAIL_MAILER', 'MAIL_HOST', 'MAIL_PORT', 'MAIL_USERNAME', 'MAIL_PASSWORD', 'MAIL_ENCRYPTION', 'MAIL_FROM_ADDRESS', 'MAIL_FROM_NAME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_DEFAULT_REGION', 'AWS_BUCKET', 'PUSHER_APP_ID', 'PUSHER_APP_KEY', 'PUSHER_APP_SECRET', 'PUSHER_APP_CLUSTER', 'MIX_PUSHER_APP_KEY', 'MIX_PUSHER_APP_CLUSTER', 'dummy_variable'];
$env_vars = ['APP_NAME', 'APP_ENV', 'APP_KEY', 'APP_DEBUG', 'APP_URL', 'LOG_CHANNEL', 'DB_CONNECTION', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USERNAME', 'DB_PASSWORD', 'BROADCAST_DRIVER', 'CACHE_DRIVER', 'QUEUE_CONNECTION', 'SESSION_DRIVER', 'SESSION_LIFETIME', 'REDIS_HOST', 'REDIS_PASSWORD', 'REDIS_PORT', 'MAIL_MAILER', 'MAIL_HOST', 'MAIL_PORT', 'MAIL_USERNAME', 'MAIL_PASSWORD', 'MAIL_ENCRYPTION', 'MAIL_FROM_ADDRESS', 'MAIL_FROM_NAME', 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_DEFAULT_REGION', 'AWS_BUCKET', 'PUSHER_APP_ID', 'PUSHER_APP_KEY', 'PUSHER_APP_SECRET', 'PUSHER_APP_CLUSTER', 'MIX_PUSHER_APP_KEY', 'MIX_PUSHER_APP_CLUSTER', 'DUMMY_VARIABLE'];

foreach ($env_vars as $key) {
$env = new Env();
Expand Down

0 comments on commit 47e1e9b

Please sign in to comment.