IniParser: Properly unescape special characters in section names and option values

refs #3648

Signed-off-by: Eric Lippmann <eric.lippmann@icinga.com>
This commit is contained in:
Johannes Meyer 2019-02-01 10:42:58 +01:00 committed by Eric Lippmann
parent 9571d1059f
commit ed7ecb32a5
4 changed files with 118 additions and 20 deletions

View File

@ -41,13 +41,18 @@ class Section
/**
* @param string $name The immutable name of this section
*
* @throws ConfigurationError When the section name is empty
* @throws ConfigurationError When the section name is empty or contains brackets
*/
public function __construct($name)
{
$this->name = trim($name);
if (strlen($this->name) < 1) {
throw new ConfigurationError(sprintf('Ini file error: empty section identifier'));
throw new ConfigurationError('Ini file error: empty section identifier');
} elseif (strpos($name, '[') !== false || strpos($name, ']') !== false) {
throw new ConfigurationError(
'Ini file error: Section name "%s" must not contain any brackets ([, ])',
$name
);
}
}
@ -165,7 +170,6 @@ class Section
$str = trim($str);
$str = str_replace('\\', '\\\\', $str);
$str = str_replace('"', '\\"', $str);
$str = str_replace(']', '\\]', $str);
$str = str_replace(';', '\\;', $str);
return str_replace(PHP_EOL, ' ', $str);
}

View File

@ -269,9 +269,38 @@ class IniParser
$unescaped = array();
foreach ($configArray as $section => $options) {
$unescaped[preg_replace('/\\\\(.)/', '\1', $section)] = $options;
$unescaped[self::unescapeSectionName($section)] = array_map([__CLASS__, 'unescapeOptionValue'], $options);
}
return Config::fromArray($unescaped)->setConfigFile($file);
}
/**
* Unescape significant characters in the given section name
*
* @param string $str
*
* @return string
*/
protected static function unescapeSectionName($str)
{
$str = str_replace('\\"', '"', $str);
$str = str_replace('\\;', ';', $str);
return str_replace('\\\\', '\\', $str);
}
/**
* Unescape significant characters in the given option value
*
* @param string $str
*
* @return string
*/
protected static function unescapeOptionValue($str)
{
$str = str_replace('\\"', '"', $str);
return str_replace('\\\\', '\\', $str);
}
}

View File

@ -3,7 +3,6 @@
namespace Tests\Icinga\Config;
use Icinga\File\Ini\Dom\Document;
use Icinga\File\Ini\IniWriter;
use Icinga\Test\BaseTestCase;
use Icinga\Application\Config;
@ -28,22 +27,45 @@ class IniParserTest extends BaseTestCase
public function testSectionNameEscaping()
{
$config = <<<'EOD'
[title with \]bracket]
key1 = "1"
key2 = "2"
[title with \"quote]
key1 = "1"
key2 = "2"
[title with \;semicolon]
[title with \\backslash]
EOD;
$doc = IniParser::parseIni($config);
$this->assertTrue(
$doc->hasSection('title with ]bracket'),
'IniParser does not recognize escaped bracket in section'
$doc->hasSection('title with "quote'),
'IniParser::parseIni does not recognize escaped quotes in section names'
);
$this->assertTrue(
$doc->hasSection('title with "quote'),
'IniParser does not recognize escaped quote in section'
$doc->hasSection('title with ;semicolon'),
'IniParser::parseIni does not recognize escaped semicolons in section names'
);
$this->assertTrue(
$doc->hasSection('title with \\backslash'),
'IniParser::parseIni does not recognize escaped backslashes in section names'
);
(new IniWriter(Config::fromArray([
'title with "quote' => [],
'title with ;semicolon' => [],
'title with \\backslash' => []
]), $this->tempFile))->write();
$configObject = IniParser::parseIniFile($this->tempFile);
$this->assertTrue(
$configObject->hasSection('title with "quote'),
'IniParser::parseIniFile does not recognize escaped quotes in section names'
);
$this->assertTrue(
$configObject->hasSection('title with ;semicolon'),
'IniParser::parseIniFile does not recognize escaped semicolons in section names'
);
$this->assertTrue(
$configObject->hasSection('title with \\backslash'),
'IniParser::parseIniFile does not recognize escaped backslashes in section names'
);
}
@ -52,12 +74,50 @@ EOD;
$config = <<<'EOD'
[section]
key1 = "key with escaped \"quote"
key2 = "key with escaped backslash \\"
key3 = "key with escaped backslash followed by quote \\\""
EOD;
$doc = IniParser::parseIni($config);
$this->assertEquals(
'key with escaped "quote',
$doc->getSection('section')->getDirective('key1')->getValue(),
'IniParser does not recognize escaped bracket in section'
'IniParser::parseIni does not recognize escaped quotes in values'
);
$this->assertEquals(
'key with escaped backslash \\',
$doc->getSection('section')->getDirective('key2')->getValue(),
'IniParser::parseIni does not recognize escaped backslashes in values'
);
$this->assertEquals(
'key with escaped backslash followed by quote \\"',
$doc->getSection('section')->getDirective('key3')->getValue(),
'IniParser::parseIni does not recognize escaped backslashes followed by quotes in values'
);
(new IniWriter(Config::fromArray([
'section' => [
'key1' => 'key with escaped "quote',
'key2' => 'key with escaped backslash \\',
'key3' => 'key with escaped backslash followed by quote \\"'
]
]), $this->tempFile))->write();
$configObject = IniParser::parseIniFile($this->tempFile);
$this->assertEquals(
'key with escaped "quote',
$configObject->getSection('section')->get('key1'),
'IniParser::parseIniFile does not recognize escaped quotes in values'
);
$this->assertEquals(
'key with escaped backslash \\',
$configObject->getSection('section')->get('key2'),
'IniParser::parseIniFile does not recognize escaped backslashes in values'
);
$this->assertEquals(
'key with escaped backslash followed by quote \\"',
$configObject->getSection('section')->get('key3'),
'IniParser::parseIniFile does not recognize escaped backslashes followed by quotes in values'
);
}

View File

@ -285,9 +285,6 @@ inkey' => 'blarg'
public function testSectionNameEscaping()
{
$config = <<<'EOD'
[section [brackets\]]
foo = "bar"
[section \;comment]
foo = "bar"
@ -304,7 +301,6 @@ EOD;
$writer = new IniWriter(
Config::fromArray(
array(
'section [brackets]' => array('foo' => 'bar'),
'section ;comment' => array('foo' => 'bar'),
'section "quotes"' => array('foo' => 'bar'),
'section with \\' => array('foo' => 'bar'),
@ -321,6 +317,15 @@ EOD;
);
}
/**
* @expectedException \Icinga\Exception\ConfigurationError
*/
public function testWhetherBracketsAreIllegalInSectionNames()
{
$config = Config::fromArray(['section [brackets]' => []]);
(new IniWriter($config, $this->tempFile))->write();
}
public function testDirectiveValueEscaping()
{
$config = <<<'EOD'