Merge branch '2.3' into 2.4

* 2.3: (34 commits)
  Fix #8205 : Deprecate file mode update when calling dumpFile
  Fix #10437: Catch exceptions when reloading a no-cache request
  Fix libxml_use_internal_errors and libxml_disable_entity_loader usage
  removed ini check to make uploadedfile work on gae
  Update OptionsResolver.php
  fixed comment in forms.xml file
  Clean KernelInterface docblocks
  Cast the group name as a string
  Fixed doc of InitAclCommand
  [Form] Fix "Array was modified outside object" in ResizeFormListener.
  Fix IBAN validator
  [Process] Remove unreachable code + avoid skipping tests in sigchild environment
  Fixed bug that incorrectly causes the "required" attribute to be omitted from select even though it contains the "multiple" attribute
  Added travis_retry to .travis.yml
  [Process] fix some typos and refactor some code
  [Process] Fix unit tests in sigchild disabled environment
  [Process] Trow exceptions in case a Process method is supposed to be called after termination
  fixed typo
  [Process] fixed fatal errors in getOutput and getErrorOutput when process was not started
  [Process] Fix escaping on Windows
  ...

Conflicts:
	src/Symfony/Component/DomCrawler/Crawler.php
	src/Symfony/Component/Filesystem/Filesystem.php
	src/Symfony/Component/Process/Process.php
This commit is contained in:
Fabien Potencier 2014-03-26 12:35:33 +01:00
commit ab42e9cbc4
50 changed files with 603 additions and 204 deletions

View File

@ -15,7 +15,7 @@ matrix:
services: mongodb services: mongodb
before_script: before_script:
- sudo apt-get install parallel - travis_retry sudo apt-get install parallel
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini; fi;' - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini; fi;'
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "extension = mongo.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;'
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ] && [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;'

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2013 Fabien Potencier Copyright (c) 2004-2014 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -67,7 +67,7 @@
{% block choice_widget_collapsed %} {% block choice_widget_collapsed %}
{% spaceless %} {% spaceless %}
{% if required and empty_value is none and not empty_value_in_choices %} {% if required and empty_value is none and not empty_value_in_choices and not multiple %}
{% set required = false %} {% set required = false %}
{% endif %} {% endif %}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}> <select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>

View File

@ -67,6 +67,9 @@ router script using <info>--router</info> option:
<info>%command.full_name% --router=app/config/router.php</info> <info>%command.full_name% --router=app/config/router.php</info>
Specifing a router script is required when the used environment is not "dev" or
"prod".
See also: http://www.php.net/manual/en/features.commandline.webserver.php See also: http://www.php.net/manual/en/features.commandline.webserver.php
EOF EOF
) )

View File

@ -20,12 +20,12 @@ class CompilerDebugDumpPass implements CompilerPassInterface
{ {
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$filename = self::getCompilerLogFilename($container);
$filesystem = new Filesystem(); $filesystem = new Filesystem();
$filesystem->dumpFile( $filesystem->dumpFile($filename, implode("\n", $container->getCompiler()->getLog()), null);
self::getCompilerLogFilename($container), // discard chmod failure (some filesystem may not support it)
implode("\n", $container->getCompiler()->getLog()), @chmod($filename, 0666 & ~umask());
0666 & ~umask()
);
} }
public static function getCompilerLogFilename(ContainerInterface $container) public static function getCompilerLogFilename(ContainerInterface $container)

View File

@ -28,11 +28,10 @@ class ContainerBuilderDebugDumpPass implements CompilerPassInterface
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$dumper = new XmlDumper($container); $dumper = new XmlDumper($container);
$filename = $container->getParameter('debug.container.dump');
$filesystem = new Filesystem(); $filesystem = new Filesystem();
$filesystem->dumpFile( $filesystem->dumpFile($filename, $dumper->dump(), null);
$container->getParameter('debug.container.dump'), // discard chmod failure (some filesystem may not support it)
$dumper->dump(), @chmod($filename, 0666 & ~umask());
0666 & ~umask()
);
} }
} }

View File

@ -23,7 +23,7 @@
We don't need to be able to add more extensions. We don't need to be able to add more extensions.
* more types can be registered with the form.type tag * more types can be registered with the form.type tag
* more type extensions can be registered with the form.type_extension tag * more type extensions can be registered with the form.type_extension tag
* more type_guessers can be registered with the form.type.type_guesser tag * more type_guessers can be registered with the form.type_guesser tag
--> -->
<argument type="service" id="form.extension" /> <argument type="service" id="form.extension" />
</argument> </argument>

View File

@ -1,6 +1,9 @@
<select <select
<?php if ($required && $empty_value === null && $empty_value_in_choices === false && $multiple === false):
$required = false;
endif; ?>
<?php echo $view['form']->block($form, 'widget_attributes', array( <?php echo $view['form']->block($form, 'widget_attributes', array(
'required' => $required && (null !== $empty_value || $empty_value_in_choices) 'required' => $required
)) ?> )) ?>
<?php if ($multiple): ?> multiple="multiple"<?php endif ?> <?php if ($multiple): ?> multiple="multiple"<?php endif ?>
> >

View File

@ -24,7 +24,7 @@ use Doctrine\DBAL\Schema\SchemaException;
class InitAclCommand extends ContainerAwareCommand class InitAclCommand extends ContainerAwareCommand
{ {
/** /**
* @see Command * {@inheritdoc}
*/ */
protected function configure() protected function configure()
{ {
@ -47,7 +47,7 @@ EOF
} }
/** /**
* @see Command::execute() * {@inheritdoc}
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {

View File

@ -95,10 +95,12 @@ class ConfigCache
{ {
$mode = 0666 & ~umask(); $mode = 0666 & ~umask();
$filesystem = new Filesystem(); $filesystem = new Filesystem();
$filesystem->dumpFile($this->file, $content, $mode); $filesystem->dumpFile($this->file, $content, null);
@chmod($this->file, $mode);
if (null !== $metadata && true === $this->debug) { if (null !== $metadata && true === $this->debug) {
$filesystem->dumpFile($this->getMetaFile(), serialize($metadata), $mode); $filesystem->dumpFile($this->getMetaFile(), serialize($metadata), null);
@chmod($this->getMetaFile(), $mode);
} }
} }

View File

@ -57,6 +57,6 @@ class DelegatingLoader extends Loader
*/ */
public function supports($resource, $type = null) public function supports($resource, $type = null)
{ {
return false === $this->resolver->resolve($resource, $type) ? false : true; return false !== $this->resolver->resolve($resource, $type);
} }
} }

View File

@ -133,6 +133,45 @@ class XmlUtilsTest extends \PHPUnit_Framework_TestCase
array(6, '0b0110'), array(6, '0b0110'),
); );
} }
public function testLoadEmptyXmlFile()
{
$file = __DIR__.'/../Fixtures/foo.xml';
$this->setExpectedException('InvalidArgumentException', 'File '.$file.' does not contain valid XML, it is empty.');
XmlUtils::loadFile($file);
}
// test for issue https://github.com/symfony/symfony/issues/9731
public function testLoadWrongEmptyXMLWithErrorHandler()
{
$originalDisableEntities = libxml_disable_entity_loader(false);
$errorReporting = error_reporting(-1);
set_error_handler(function ($errno, $errstr) {
throw new \Exception($errstr, $errno);
});
$file = __DIR__.'/../Fixtures/foo.xml';
try {
XmlUtils::loadFile($file);
$this->fail('An exception should have been raised');
} catch (\InvalidArgumentException $e) {
$this->assertEquals(sprintf('File %s does not contain valid XML, it is empty.', $file), $e->getMessage());
}
restore_error_handler();
error_reporting($errorReporting);
$disableEntities = libxml_disable_entity_loader(true);
libxml_disable_entity_loader($disableEntities);
libxml_disable_entity_loader($originalDisableEntities);
$this->assertFalse($disableEntities);
// should not throw an exception
XmlUtils::loadFile(__DIR__.'/../Fixtures/Util/valid.xml', __DIR__.'/../Fixtures/Util/schema.xsd');
}
} }
interface Validator interface Validator

View File

@ -40,13 +40,18 @@ class XmlUtils
*/ */
public static function loadFile($file, $schemaOrCallable = null) public static function loadFile($file, $schemaOrCallable = null)
{ {
$content = @file_get_contents($file);
if ('' === trim($content)) {
throw new \InvalidArgumentException(sprintf('File %s does not contain valid XML, it is empty.', $file));
}
$internalErrors = libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true); $disableEntities = libxml_disable_entity_loader(true);
libxml_clear_errors(); libxml_clear_errors();
$dom = new \DOMDocument(); $dom = new \DOMDocument();
$dom->validateOnParse = true; $dom->validateOnParse = true;
if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { if (!$dom->loadXML($content, LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
libxml_disable_entity_loader($disableEntities); libxml_disable_entity_loader($disableEntities);
throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors))); throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors)));

View File

@ -49,7 +49,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$translator->registerExtension(new HtmlExtension($translator)); $translator->registerExtension(new HtmlExtension($translator));
$document = new \DOMDocument(); $document = new \DOMDocument();
$document->strictErrorChecking = false; $document->strictErrorChecking = false;
libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$document->loadHTMLFile(__DIR__.'/Fixtures/ids.html'); $document->loadHTMLFile(__DIR__.'/Fixtures/ids.html');
$document = simplexml_import_dom($document); $document = simplexml_import_dom($document);
$elements = $document->xpath($translator->cssToXPath($css)); $elements = $document->xpath($translator->cssToXPath($css));
@ -59,6 +59,8 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue(in_array($element->attributes()->id, $elementsId)); $this->assertTrue(in_array($element->attributes()->id, $elementsId));
} }
} }
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
} }
/** @dataProvider getHtmlShakespearTestData */ /** @dataProvider getHtmlShakespearTestData */

View File

@ -151,7 +151,7 @@ class Crawler extends \SplObjectStorage
*/ */
public function addHtmlContent($content, $charset = 'UTF-8') public function addHtmlContent($content, $charset = 'UTF-8')
{ {
$current = libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true); $disableEntities = libxml_disable_entity_loader(true);
$dom = new \DOMDocument('1.0', $charset); $dom = new \DOMDocument('1.0', $charset);
@ -171,9 +171,11 @@ class Crawler extends \SplObjectStorage
} }
} }
@$dom->loadHTML($content); if ('' !== trim($content)) {
@$dom->loadHTML($content);
}
libxml_use_internal_errors($current); libxml_use_internal_errors($internalErrors);
libxml_disable_entity_loader($disableEntities); libxml_disable_entity_loader($disableEntities);
$this->addDocument($dom); $this->addDocument($dom);
@ -215,14 +217,18 @@ class Crawler extends \SplObjectStorage
$content = str_replace('xmlns', 'ns', $content); $content = str_replace('xmlns', 'ns', $content);
} }
$current = libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true); $disableEntities = libxml_disable_entity_loader(true);
$dom = new \DOMDocument('1.0', $charset); $dom = new \DOMDocument('1.0', $charset);
$dom->validateOnParse = true; $dom->validateOnParse = true;
@$dom->loadXML($content, LIBXML_NONET);
libxml_use_internal_errors($current); if ('' !== trim($content)) {
// remove the default namespace to make XPath expressions simpler
@$dom->loadXML(str_replace('xmlns', 'ns', $content), LIBXML_NONET);
}
libxml_use_internal_errors($internalErrors);
libxml_disable_entity_loader($disableEntities); libxml_disable_entity_loader($disableEntities);
$this->addDocument($dom); $this->addDocument($dom);

View File

@ -143,8 +143,13 @@ class Form extends Link implements \ArrayAccess
*/ */
public function getPhpValues() public function getPhpValues()
{ {
$qs = http_build_query($this->getValues(), '', '&'); $values = array();
parse_str($qs, $values); foreach ($this->getValues() as $name => $value) {
$qs = http_build_query(array($name => $value), '', '&');
parse_str($qs, $expandedValue);
$varName = substr($name, 0, strlen(key($expandedValue)));
$values = array_replace_recursive($values, array($varName => current($expandedValue)));
}
return $values; return $values;
} }
@ -161,8 +166,13 @@ class Form extends Link implements \ArrayAccess
*/ */
public function getPhpFiles() public function getPhpFiles()
{ {
$qs = http_build_query($this->getFiles(), '', '&'); $values = array();
parse_str($qs, $values); foreach ($this->getFiles() as $name => $value) {
$qs = http_build_query(array($name => $value), '', '&');
parse_str($qs, $expandedValue);
$varName = substr($name, 0, strlen(key($expandedValue)));
$values = array_replace_recursive($values, array($varName => current($expandedValue)));
}
return $values; return $values;
} }
@ -414,8 +424,7 @@ class Form extends Link implements \ArrayAccess
// restore the original name of the input node // restore the original name of the input node
$this->button->setAttribute('name', $name); $this->button->setAttribute('name', $name);
} } else {
else {
$this->set(new Field\InputFormField($document->importNode($this->button, true))); $this->set(new Field\InputFormField($document->importNode($this->button, true)));
} }
} }

View File

@ -130,7 +130,7 @@ class CrawlerTest extends \PHPUnit_Framework_TestCase
*/ */
public function testAddHtmlContentWithErrors() public function testAddHtmlContentWithErrors()
{ {
libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$crawler = new Crawler(); $crawler = new Crawler();
$crawler->addHtmlContent(<<<EOF $crawler->addHtmlContent(<<<EOF
@ -150,7 +150,7 @@ EOF
$this->assertEquals("Tag nav invalid\n", $errors[0]->message); $this->assertEquals("Tag nav invalid\n", $errors[0]->message);
libxml_clear_errors(); libxml_clear_errors();
libxml_use_internal_errors(false); libxml_use_internal_errors($internalErrors);
} }
/** /**
@ -180,7 +180,7 @@ EOF
*/ */
public function testAddXmlContentWithErrors() public function testAddXmlContentWithErrors()
{ {
libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$crawler = new Crawler(); $crawler = new Crawler();
$crawler->addXmlContent(<<<EOF $crawler->addXmlContent(<<<EOF
@ -198,7 +198,7 @@ EOF
$this->assertTrue(count(libxml_get_errors()) > 1); $this->assertTrue(count(libxml_get_errors()) > 1);
libxml_clear_errors(); libxml_clear_errors();
libxml_use_internal_errors(false); libxml_use_internal_errors($internalErrors);
} }
/** /**

View File

@ -411,6 +411,12 @@ class FormTest extends \PHPUnit_Framework_TestCase
{ {
$form = $this->createForm('<form><input type="text" name="foo[bar]" value="foo" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>'); $form = $this->createForm('<form><input type="text" name="foo[bar]" value="foo" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), '->getPhpValues() converts keys with [] to arrays'); $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), '->getPhpValues() converts keys with [] to arrays');
$form = $this->createForm('<form><input type="text" name="fo.o[ba.r]" value="foo" /><input type="text" name="ba r" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('fo.o' => array('ba.r' => 'foo'), 'ba r' => 'bar'), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names');
$form = $this->createForm('<form><input type="text" name="fo.o[ba.r][]" value="foo" /><input type="text" name="fo.o[ba.r][ba.z]" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('fo.o' => array('ba.r' => array('foo', 'ba.z' => 'bar'))), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names recursively');
} }
public function testGetFiles() public function testGetFiles()
@ -438,6 +444,12 @@ class FormTest extends \PHPUnit_Framework_TestCase
{ {
$form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>'); $form = $this->createForm('<form method="post"><input type="file" name="foo[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('foo' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() converts keys with [] to arrays'); $this->assertEquals(array('foo' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() converts keys with [] to arrays');
$form = $this->createForm('<form method="post"><input type="file" name="f.o o[bar]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('f.o o' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names');
$form = $this->createForm('<form method="post"><input type="file" name="f.o o[bar][ba.z]" /><input type="file" name="f.o o[bar][]" /><input type="text" name="bar" value="bar" /><input type="submit" /></form>');
$this->assertEquals(array('f.o o' => array('bar' => array('ba.z' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0), array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names recursively');
} }
/** /**

View File

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
2.3.12
------
* deprecated dumpFile() file mode argument.
2.3.0 2.3.0
----- -----

View File

@ -433,10 +433,11 @@ class Filesystem
/** /**
* Atomically dumps content into a file. * Atomically dumps content into a file.
* *
* @param string $filename The file to be written to. * @param string $filename The file to be written to.
* @param string $content The data to write into the file. * @param string $content The data to write into the file.
* @param integer $mode The file mode (octal). * @param null|integer $mode The file mode (octal). If null, file permissions are not modified
* @throws IOException If the file cannot be written to. * Deprecated since version 2.3.12, to be removed in 3.0.
* @throws IOException If the file cannot be written to.
*/ */
public function dumpFile($filename, $content, $mode = 0666) public function dumpFile($filename, $content, $mode = 0666)
{ {
@ -455,7 +456,9 @@ class Filesystem
} }
$this->rename($tmpFile, $filename, true); $this->rename($tmpFile, $filename, true);
$this->chmod($filename, $mode); if (null !== $mode) {
$this->chmod($filename, $mode);
}
} }
/** /**

View File

@ -879,6 +879,21 @@ class FilesystemTest extends FilesystemTestCase
} }
} }
public function testDumpFileWithNullMode()
{
$filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt';
$this->filesystem->dumpFile($filename, 'bar', null);
$this->assertFileExists($filename);
$this->assertSame('bar', file_get_contents($filename));
// skip mode check on Windows
if (!defined('PHP_WINDOWS_VERSION_MAJOR')) {
$this->assertEquals(600, $this->getFilePermissions($filename));
}
}
public function testDumpFileOverwritesAnExistingFile() public function testDumpFileOverwritesAnExistingFile()
{ {
$filename = $this->workspace.DIRECTORY_SEPARATOR.'foo.txt'; $filename = $this->workspace.DIRECTORY_SEPARATOR.'foo.txt';

View File

@ -128,11 +128,13 @@ class ObjectChoiceList extends ChoiceList
if (null === $group) { if (null === $group) {
$groupedChoices[$i] = $choice; $groupedChoices[$i] = $choice;
} else { } else {
if (!isset($groupedChoices[$group])) { $groupName = (string) $group;
$groupedChoices[$group] = array();
if (!isset($groupedChoices[$groupName])) {
$groupedChoices[$groupName] = array();
} }
$groupedChoices[$group][$i] = $choice; $groupedChoices[$groupName][$i] = $choice;
} }
} }

View File

@ -139,11 +139,17 @@ class ResizeFormListener implements EventSubscriberInterface
// The data mapper only adds, but does not remove items, so do this // The data mapper only adds, but does not remove items, so do this
// here // here
if ($this->allowDelete) { if ($this->allowDelete) {
$toDelete = array();
foreach ($data as $name => $child) { foreach ($data as $name => $child) {
if (!$form->has($name)) { if (!$form->has($name)) {
unset($data[$name]); $toDelete[] = $name;
} }
} }
foreach ($toDelete as $name) {
unset($data[$name]);
}
} }
$event->setData($data); $event->setData($data);

View File

@ -636,6 +636,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
{ {
$form = $this->factory->createNamed('name', 'choice', array('&a'), array( $form = $this->factory->createNamed('name', 'choice', array('&a'), array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'), 'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'required' => true,
'multiple' => true, 'multiple' => true,
'expanded' => false, 'expanded' => false,
)); ));
@ -643,6 +644,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
$this->assertWidgetMatchesXpath($form->createView(), array(), $this->assertWidgetMatchesXpath($form->createView(), array(),
'/select '/select
[@name="name[]"] [@name="name[]"]
[@required="required"]
[@multiple="multiple"] [@multiple="multiple"]
[ [
./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] ./option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]

View File

@ -250,7 +250,20 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(), $event->getData()); $this->assertEquals(array(), $event->getData());
} }
public function testOnSubmitDealsWithIteratorAggregate() public function testOnSubmitDealsWithObjectBackedIteratorAggregate()
{
$this->form->add($this->getForm('1'));
$data = new \ArrayObject(array(0 => 'first', 1 => 'second', 2 => 'third'));
$event = new FormEvent($this->form, $data);
$listener = new ResizeFormListener('text', array(), false, true);
$listener->onSubmit($event);
$this->assertArrayNotHasKey(0, $event->getData());
$this->assertArrayNotHasKey(2, $event->getData());
}
public function testOnSubmitDealsWithArrayBackedIteratorAggregate()
{ {
$this->form->add($this->getForm('1')); $this->form->add($this->getForm('1'));

View File

@ -91,10 +91,6 @@ class UploadedFile extends File
*/ */
public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false)
{ {
if (!ini_get('file_uploads')) {
throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path')));
}
$this->originalName = $this->getName($originalName); $this->originalName = $this->getName($originalName);
$this->mimeType = $mimeType ?: 'application/octet-stream'; $this->mimeType = $mimeType ?: 'application/octet-stream';
$this->size = $size; $this->size = $size;
@ -108,7 +104,7 @@ class UploadedFile extends File
* Returns the original file name. * Returns the original file name.
* *
* It is extracted from the request from which the file has been uploaded. * It is extracted from the request from which the file has been uploaded.
* Then is should not be considered as a safe value. * Then it should not be considered as a safe value.
* *
* @return string|null The original name * @return string|null The original name
* *
@ -123,7 +119,7 @@ class UploadedFile extends File
* Returns the original file extension * Returns the original file extension
* *
* It is extracted from the original file name that was uploaded. * It is extracted from the original file name that was uploaded.
* Then is should not be considered as a safe value. * Then it should not be considered as a safe value.
* *
* @return string The extension * @return string The extension
*/ */
@ -181,7 +177,7 @@ class UploadedFile extends File
* Returns the file size. * Returns the file size.
* *
* It is extracted from the request from which the file has been uploaded. * It is extracted from the request from which the file has been uploaded.
* Then is should not be considered as a safe value. * Then it should not be considered as a safe value.
* *
* @return integer|null The file size * @return integer|null The file size
* *

View File

@ -308,7 +308,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
if ($this->options['allow_reload'] && $request->isNoCache()) { if ($this->options['allow_reload'] && $request->isNoCache()) {
$this->record($request, 'reload'); $this->record($request, 'reload');
return $this->fetch($request); return $this->fetch($request, $catch);
} }
try { try {

View File

@ -27,7 +27,7 @@ use Symfony\Component\Config\Loader\LoaderInterface;
interface KernelInterface extends HttpKernelInterface, \Serializable interface KernelInterface extends HttpKernelInterface, \Serializable
{ {
/** /**
* Returns an array of bundles to registers. * Returns an array of bundles to register.
* *
* @return BundleInterface[] An array of bundle instances. * @return BundleInterface[] An array of bundle instances.
* *
@ -36,7 +36,7 @@ interface KernelInterface extends HttpKernelInterface, \Serializable
public function registerBundles(); public function registerBundles();
/** /**
* Loads the container configuration * Loads the container configuration.
* *
* @param LoaderInterface $loader A LoaderInterface instance * @param LoaderInterface $loader A LoaderInterface instance
* *
@ -125,7 +125,7 @@ interface KernelInterface extends HttpKernelInterface, \Serializable
public function locateResource($name, $dir = null, $first = true); public function locateResource($name, $dir = null, $first = true);
/** /**
* Gets the name of the kernel * Gets the name of the kernel.
* *
* @return string The kernel name * @return string The kernel name
* *

View File

@ -923,6 +923,17 @@ class HttpCacheTest extends HttpCacheTestCase
$this->assertExceptionsAreCaught(); $this->assertExceptionsAreCaught();
} }
public function testShouldCatchExceptionsWhenReloadingAndNoCacheRequest()
{
$this->catchExceptions();
$this->setNextResponse();
$this->cacheConfig['allow_reload'] = true;
$this->request('GET', '/', array(), array(), false, array('Pragma' => 'no-cache'));
$this->assertExceptionsAreCaught();
}
public function testShouldNotCatchExceptions() public function testShouldNotCatchExceptions()
{ {
$this->catchExceptions(false); $this->catchExceptions(false);

View File

@ -103,7 +103,7 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
$this->assertFalse($this->kernel->isCatchingExceptions()); $this->assertFalse($this->kernel->isCatchingExceptions());
} }
public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false) public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false, $headers = array())
{ {
if (null === $this->kernel) { if (null === $this->kernel) {
throw new \LogicException('You must call setNextResponse() before calling request().'); throw new \LogicException('You must call setNextResponse() before calling request().');
@ -118,6 +118,7 @@ class HttpCacheTestCase extends \PHPUnit_Framework_TestCase
$this->esi = $esi ? new Esi() : null; $this->esi = $esi ? new Esi() : null;
$this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig); $this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig);
$this->request = Request::create($uri, $method, array(), $cookies, array(), $server); $this->request = Request::create($uri, $method, array(), $cookies, array(), $server);
$this->request->headers->add($headers);
$this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch); $this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch);

View File

@ -276,7 +276,7 @@ class OptionsResolver implements OptionsResolverInterface
ksort($diff); ksort($diff);
throw new MissingOptionsException(sprintf( throw new MissingOptionsException(sprintf(
count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.', count($diff) > 1 ? 'The required options "%s" are missing.' : 'The required option "%s" is missing.',
implode('", "', array_keys($diff)) implode('", "', array_keys($diff))
)); ));
} }

View File

@ -59,8 +59,8 @@ class Process
private $enhanceSigchildCompatibility; private $enhanceSigchildCompatibility;
private $process; private $process;
private $status = self::STATUS_READY; private $status = self::STATUS_READY;
private $incrementalOutputOffset; private $incrementalOutputOffset = 0;
private $incrementalErrorOutputOffset; private $incrementalErrorOutputOffset = 0;
private $tty; private $tty;
private $useFileHandles = false; private $useFileHandles = false;
@ -189,7 +189,8 @@ class Process
* *
* @return integer The exit status code * @return integer The exit status code
* *
* @throws RuntimeException When process can't be launch or is stopped * @throws RuntimeException When process can't be launched
* @throws RuntimeException When process stopped after receiving signal
* *
* @api * @api
*/ */
@ -220,7 +221,7 @@ class Process
* *
* @return Process The process itself * @return Process The process itself
* *
* @throws RuntimeException When process can't be launch or is stopped * @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running * @throws RuntimeException When process is already running
*/ */
public function start($callback = null) public function start($callback = null)
@ -237,7 +238,11 @@ class Process
$commandline = $this->commandline; $commandline = $this->commandline;
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) { if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) {
$commandline = 'cmd /V:ON /E:ON /C "'.$commandline.'"'; $commandline = 'cmd /V:ON /E:ON /C "('.$commandline.')"';
foreach ($this->processPipes->getFiles() as $offset => $filename) {
$commandline .= ' '.$offset.'>'.$filename;
}
if (!isset($this->options['bypass_shell'])) { if (!isset($this->options['bypass_shell'])) {
$this->options['bypass_shell'] = true; $this->options['bypass_shell'] = true;
} }
@ -271,7 +276,7 @@ class Process
* *
* @return Process The new process * @return Process The new process
* *
* @throws RuntimeException When process can't be launch or is stopped * @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running * @throws RuntimeException When process is already running
* *
* @see start() * @see start()
@ -301,9 +306,12 @@ class Process
* *
* @throws RuntimeException When process timed out * @throws RuntimeException When process timed out
* @throws RuntimeException When process stopped after receiving signal * @throws RuntimeException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/ */
public function wait($callback = null) public function wait($callback = null)
{ {
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false); $this->updateStatus(false);
if (null !== $callback) { if (null !== $callback) {
$this->callback = $this->buildCallback($callback); $this->callback = $this->buildCallback($callback);
@ -321,10 +329,6 @@ class Process
} }
if ($this->processInformation['signaled']) { if ($this->processInformation['signaled']) {
if ($this->isSigchildEnabled()) {
throw new RuntimeException('The process has been signaled.');
}
throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
} }
@ -353,6 +357,7 @@ class Process
* Sends a POSIX signal to the process. * Sends a POSIX signal to the process.
* *
* @param integer $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) * @param integer $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
*
* @return Process * @return Process
* *
* @throws LogicException In case the process is not running * @throws LogicException In case the process is not running
@ -361,17 +366,7 @@ class Process
*/ */
public function signal($signal) public function signal($signal)
{ {
if (!$this->isRunning()) { $this->doSignal($signal, true);
throw new LogicException('Can not send signal on a non running process.');
}
if ($this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
}
if (true !== @proc_terminate($this->process, $signal)) {
throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal));
}
return $this; return $this;
} }
@ -381,10 +376,14 @@ class Process
* *
* @return string The process output * @return string The process output
* *
* @throws LogicException In case the process is not started
*
* @api * @api
*/ */
public function getOutput() public function getOutput()
{ {
$this->requireProcessIsStarted(__FUNCTION__);
$this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true); $this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
return $this->stdout; return $this->stdout;
@ -396,10 +395,14 @@ class Process
* In comparison with the getOutput method which always return the whole * In comparison with the getOutput method which always return the whole
* output, this one returns the new output since the last call. * output, this one returns the new output since the last call.
* *
* @throws LogicException In case the process is not started
*
* @return string The process output since the last call * @return string The process output since the last call
*/ */
public function getIncrementalOutput() public function getIncrementalOutput()
{ {
$this->requireProcessIsStarted(__FUNCTION__);
$data = $this->getOutput(); $data = $this->getOutput();
$latest = substr($data, $this->incrementalOutputOffset); $latest = substr($data, $this->incrementalOutputOffset);
@ -426,10 +429,14 @@ class Process
* *
* @return string The process error output * @return string The process error output
* *
* @throws LogicException In case the process is not started
*
* @api * @api
*/ */
public function getErrorOutput() public function getErrorOutput()
{ {
$this->requireProcessIsStarted(__FUNCTION__);
$this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true); $this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
return $this->stderr; return $this->stderr;
@ -442,10 +449,14 @@ class Process
* whole error output, this one returns the new error output since the last * whole error output, this one returns the new error output since the last
* call. * call.
* *
* @throws LogicException In case the process is not started
*
* @return string The process error output since the last call * @return string The process error output since the last call
*/ */
public function getIncrementalErrorOutput() public function getIncrementalErrorOutput()
{ {
$this->requireProcessIsStarted(__FUNCTION__);
$data = $this->getErrorOutput(); $data = $this->getErrorOutput();
$latest = substr($data, $this->incrementalErrorOutputOffset); $latest = substr($data, $this->incrementalErrorOutputOffset);
@ -470,7 +481,7 @@ class Process
/** /**
* Returns the exit code returned by the process. * Returns the exit code returned by the process.
* *
* @return integer The exit status code * @return null|integer The exit status code, null if the Process is not terminated
* *
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
* *
@ -493,14 +504,18 @@ class Process
* This method relies on the Unix exit code status standardization * This method relies on the Unix exit code status standardization
* and might not be relevant for other operating systems. * and might not be relevant for other operating systems.
* *
* @return string A string representation for the exit status code * @return null|string A string representation for the exit status code, null if the Process is not terminated.
*
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
* *
* @see http://tldp.org/LDP/abs/html/exitcodes.html * @see http://tldp.org/LDP/abs/html/exitcodes.html
* @see http://en.wikipedia.org/wiki/Unix_signal * @see http://en.wikipedia.org/wiki/Unix_signal
*/ */
public function getExitCodeText() public function getExitCodeText()
{ {
$exitcode = $this->getExitCode(); if (null === $exitcode = $this->getExitCode()) {
return;
}
return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
} }
@ -525,11 +540,14 @@ class Process
* @return Boolean * @return Boolean
* *
* @throws RuntimeException In case --enable-sigchild is activated * @throws RuntimeException In case --enable-sigchild is activated
* @throws LogicException In case the process is not terminated
* *
* @api * @api
*/ */
public function hasBeenSignaled() public function hasBeenSignaled()
{ {
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled()) { if ($this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
} }
@ -547,11 +565,14 @@ class Process
* @return integer * @return integer
* *
* @throws RuntimeException In case --enable-sigchild is activated * @throws RuntimeException In case --enable-sigchild is activated
* @throws LogicException In case the process is not terminated
* *
* @api * @api
*/ */
public function getTermSignal() public function getTermSignal()
{ {
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled()) { if ($this->isSigchildEnabled()) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
} }
@ -568,10 +589,14 @@ class Process
* *
* @return Boolean * @return Boolean
* *
* @throws LogicException In case the process is not terminated
*
* @api * @api
*/ */
public function hasBeenStopped() public function hasBeenStopped()
{ {
$this->requireProcessIsTerminated(__FUNCTION__);
$this->updateStatus(false); $this->updateStatus(false);
return $this->processInformation['stopped']; return $this->processInformation['stopped'];
@ -584,10 +609,14 @@ class Process
* *
* @return integer * @return integer
* *
* @throws LogicException In case the process is not terminated
*
* @api * @api
*/ */
public function getStopSignal() public function getStopSignal()
{ {
$this->requireProcessIsTerminated(__FUNCTION__);
$this->updateStatus(false); $this->updateStatus(false);
return $this->processInformation['stopsig']; return $this->processInformation['stopsig'];
@ -659,6 +688,12 @@ class Process
{ {
$timeoutMicro = microtime(true) + $timeout; $timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) { if ($this->isRunning()) {
if (defined('PHP_WINDOWS_VERSION_BUILD') && !$this->isSigchildEnabled()) {
exec(sprintf("taskkill /F /T /PID %d 2>&1", $this->getPid()), $output, $exitCode);
if ($exitCode > 0) {
throw new RuntimeException('Unable to kill the process');
}
}
proc_terminate($this->process); proc_terminate($this->process);
do { do {
usleep(1000); usleep(1000);
@ -666,7 +701,11 @@ class Process
if ($this->isRunning() && !$this->isSigchildEnabled()) { if ($this->isRunning() && !$this->isSigchildEnabled()) {
if (null !== $signal || defined('SIGKILL')) { if (null !== $signal || defined('SIGKILL')) {
$this->signal($signal ?: SIGKILL); // avoid exception here :
// process is supposed to be running, but it might have stop
// just after this line.
// in any case, let's silently discard the error, we can not do anything
$this->doSignal($signal ?: SIGKILL, false);
} }
} }
} }
@ -676,8 +715,6 @@ class Process
$this->close(); $this->close();
} }
$this->status = self::STATUS_TERMINATED;
return $this->exitcode; return $this->exitcode;
} }
@ -987,6 +1024,10 @@ class Process
*/ */
public function checkTimeout() public function checkTimeout()
{ {
if ($this->status !== self::STATUS_STARTED) {
return;
}
if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
$this->stop(0); $this->stop(0);
@ -1053,7 +1094,7 @@ class Process
/** /**
* Updates the status of the process, reads pipes. * Updates the status of the process, reads pipes.
* *
* @param Boolean $blocking Whether to use a clocking read call. * @param Boolean $blocking Whether to use a blocking read call.
*/ */
protected function updateStatus($blocking) protected function updateStatus($blocking)
{ {
@ -1068,7 +1109,6 @@ class Process
if (!$this->processInformation['running']) { if (!$this->processInformation['running']) {
$this->close(); $this->close();
$this->status = self::STATUS_TERMINATED;
} }
} }
@ -1153,17 +1193,17 @@ class Process
*/ */
private function close() private function close()
{ {
$exitcode = -1;
$this->processPipes->close(); $this->processPipes->close();
if (is_resource($this->process)) { if (is_resource($this->process)) {
$exitcode = proc_close($this->process); $exitcode = proc_close($this->process);
} else {
$exitcode = -1;
} }
$this->exitcode = $this->exitcode !== null ? $this->exitcode : -1; $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);
$this->exitcode = -1 != $exitcode ? $exitcode : $this->exitcode; $this->status = self::STATUS_TERMINATED;
if (-1 == $this->exitcode && null !== $this->fallbackExitcode) { if (-1 === $this->exitcode && null !== $this->fallbackExitcode) {
$this->exitcode = $this->fallbackExitcode; $this->exitcode = $this->fallbackExitcode;
} elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
@ -1190,4 +1230,73 @@ class Process
$this->incrementalOutputOffset = 0; $this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0; $this->incrementalErrorOutputOffset = 0;
} }
/**
* Sends a POSIX signal to the process.
*
* @param integer $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param Boolean $throwException Whether to throw exception in case signal failed
*
* @return Boolean True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated
* @throws RuntimeException In case of failure
*/
private function doSignal($signal, $throwException)
{
if (!$this->isRunning()) {
if ($throwException) {
throw new LogicException('Can not send signal on a non running process.');
}
return false;
}
if ($this->isSigchildEnabled()) {
if ($throwException) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
}
return false;
}
if (true !== @proc_terminate($this->process, $signal)) {
if ($throwException) {
throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
}
return false;
}
return true;
}
/**
* Ensures the process is running or terminated, throws a LogicException if the process has a not started.
*
* @param $functionName The function name that was called.
*
* @throws LogicException If the process has not run.
*/
private function requireProcessIsStarted($functionName)
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
}
}
/**
* Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
*
* @param $functionName The function name that was called.
*
* @throws LogicException If the process is not yet terminated.
*/
private function requireProcessIsTerminated($functionName)
{
if (!$this->isTerminated()) {
throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
}
}
} }

View File

@ -21,6 +21,8 @@ class ProcessPipes
/** @var array */ /** @var array */
public $pipes = array(); public $pipes = array();
/** @var array */ /** @var array */
private $files = array();
/** @var array */
private $fileHandles = array(); private $fileHandles = array();
/** @var array */ /** @var array */
private $readBytes = array(); private $readBytes = array();
@ -29,6 +31,8 @@ class ProcessPipes
/** @var Boolean */ /** @var Boolean */
private $ttyMode; private $ttyMode;
const CHUNK_SIZE = 16384;
public function __construct($useFiles, $ttyMode) public function __construct($useFiles, $ttyMode)
{ {
$this->useFiles = (Boolean) $useFiles; $this->useFiles = (Boolean) $useFiles;
@ -37,20 +41,21 @@ class ProcessPipes
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform. // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
// //
// Please note that this work around prevents hanging but
// another issue occurs : In some race conditions, some data may be
// lost or corrupted.
//
// @see https://bugs.php.net/bug.php?id=51800 // @see https://bugs.php.net/bug.php?id=51800
if ($this->useFiles) { if ($this->useFiles) {
$this->fileHandles = array( $this->files = array(
Process::STDOUT => tmpfile(), Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'),
Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'),
); );
if (false === $this->fileHandles[Process::STDOUT]) { foreach ($this->files as $offset => $file) {
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb');
if (false === $this->fileHandles[$offset]) {
throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable');
}
} }
$this->readBytes = array( $this->readBytes = array(
Process::STDOUT => 0, Process::STDOUT => 0,
Process::STDERR => 0,
); );
} }
} }
@ -58,6 +63,7 @@ class ProcessPipes
public function __destruct() public function __destruct()
{ {
$this->close(); $this->close();
$this->removeFiles();
} }
/** /**
@ -103,11 +109,13 @@ class ProcessPipes
public function getDescriptors() public function getDescriptors()
{ {
if ($this->useFiles) { if ($this->useFiles) {
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
// So we redirect output within the commandline and pass the nul device to the process
return array( return array(
array('pipe', 'r'), array('pipe', 'r'),
$this->fileHandles[Process::STDOUT], array('file', 'NUL', 'w'),
// Use a file handle only for STDOUT. Using for both STDOUT and STDERR would trigger https://bugs.php.net/bug.php?id=65650 array('file', 'NUL', 'w'),
array('pipe', 'w'),
); );
} }
@ -126,6 +134,20 @@ class ProcessPipes
); );
} }
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return array
*/
public function getFiles()
{
if ($this->useFiles) {
return $this->files;
}
return array();
}
/** /**
* Reads data in file handles and pipes. * Reads data in file handles and pipes.
* *
@ -233,7 +255,7 @@ class ProcessPipes
$data = ''; $data = '';
$dataread = null; $dataread = null;
while (!feof($fileHandle)) { while (!feof($fileHandle)) {
if (false !== $dataread = fread($fileHandle, 16392)) { if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) {
$data .= $dataread; $data .= $dataread;
} }
} }
@ -291,7 +313,7 @@ class ProcessPipes
$type = array_search($pipe, $this->pipes); $type = array_search($pipe, $this->pipes);
$data = ''; $data = '';
while ($dataread = fread($pipe, 8192)) { while ($dataread = fread($pipe, self::CHUNK_SIZE)) {
$data .= $dataread; $data .= $dataread;
} }
@ -320,4 +342,17 @@ class ProcessPipes
// stream_select returns false when the `select` system call is interrupted by an incoming signal // stream_select returns false when the `select` system call is interrupted by an incoming signal
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
} }
/**
* Removes temporary files
*/
private function removeFiles()
{
foreach ($this->files as $filename) {
if (file_exists($filename)) {
@unlink($filename);
}
}
$this->files = array();
}
} }

View File

@ -46,22 +46,34 @@ class ProcessUtils
} }
$escapedArgument = ''; $escapedArgument = '';
foreach (preg_split('/([%"])/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { $quote = false;
foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
if ('"' === $part) { if ('"' === $part) {
$escapedArgument .= '\\"'; $escapedArgument .= '\\"';
} elseif ('%' === $part) { } elseif (self::isSurroundedBy($part, '%')) {
$escapedArgument .= '^%'; // Avoid environment variable expansion
$escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
} else { } else {
// escape trailing backslash
if ('\\' === substr($part, -1)) { if ('\\' === substr($part, -1)) {
$part .= '\\'; $part .= '\\';
} }
$escapedArgument .= escapeshellarg($part); $quote = true;
$escapedArgument .= $part;
} }
} }
if ($quote) {
$escapedArgument = '"'.$escapedArgument.'"';
}
return $escapedArgument; return $escapedArgument;
} }
return escapeshellarg($argument); return escapeshellarg($argument);
} }
private static function isSurroundedBy($arg, $char)
{
return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
}
} }

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Process\Tests;
use Symfony\Component\Process\Exception\ProcessTimedOutException; use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException; use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\ProcessPipes;
/** /**
* @author Robert Schönthal <seroscho@googlemail.com> * @author Robert Schönthal <seroscho@googlemail.com>
@ -88,18 +89,20 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
// has terminated so the internal pipes array is already empty. normally // has terminated so the internal pipes array is already empty. normally
// the call to start() will not read any data as the process will not have // the call to start() will not read any data as the process will not have
// generated output, but this is non-deterministic so we must count it as // generated output, but this is non-deterministic so we must count it as
// a possibility. therefore we need 2 * 8192 plus another byte which will // a possibility. therefore we need 2 * ProcessPipes::CHUNK_SIZE plus
// never be read. // another byte which will never be read.
$expectedOutputSize = 16385; $expectedOutputSize = ProcessPipes::CHUNK_SIZE * 2 + 2;
$code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize); $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
$p->start(); $p->start();
usleep(250000); // Let's wait enough time for process to finish...
// Here we don't call Process::run or Process::wait to avoid any read of pipes
usleep(500000);
if ($p->isRunning()) { if ($p->isRunning()) {
$this->fail('Process execution did not complete in the required time frame'); $this->markTestSkipped('Process execution did not complete in the required time frame');
} }
$o = $p->getOutput(); $o = $p->getOutput();
@ -201,7 +204,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetIncrementalErrorOutput() public function testGetIncrementalErrorOutput()
{ {
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { usleep(50000); file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { usleep(100000); file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
$p->start(); $p->start();
while ($p->isRunning()) { while ($p->isRunning()) {
@ -266,7 +269,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does have /dev/tty support'); $this->markTestSkipped('Windows does have /dev/tty support');
} }
$process = $this->getProcess('echo "foo" >> /dev/null'); $process = $this->getProcess('echo "foo" >> /dev/null && php -r "usleep(100000);"');
$process->setTTY(true); $process->setTTY(true);
$process->start(); $process->start();
$this->assertTrue($process->isRunning()); $this->assertTrue($process->isRunning());
@ -288,6 +291,12 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($process->isSuccessful()); $this->assertTrue($process->isSuccessful());
} }
public function testExitCodeTextIsNullWhenExitCodeIsNull()
{
$process = $this->getProcess('');
$this->assertNull($process->getExitCodeText());
}
public function testExitCodeText() public function testExitCodeText()
{ {
$process = $this->getProcess(''); $process = $this->getProcess('');
@ -487,7 +496,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$process1->run(); $process1->run();
$process2 = $process1->restart(); $process2 = $process1->restart();
usleep(300000); // wait for output $process2->wait(); // wait for output
// Ensure that both processed finished and the output is numeric // Ensure that both processed finished and the output is numeric
$this->assertFalse($process1->isRunning()); $this->assertFalse($process1->isRunning());
@ -528,6 +537,19 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration); $this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration);
} }
public function testCheckTimeoutOnNonStartedProcess()
{
$process = $this->getProcess('php -r "sleep(3);"');
$process->checkTimeout();
}
public function testCheckTimeoutOnTerminatedProcess()
{
$process = $this->getProcess('php -v');
$process->run();
$process->checkTimeout();
}
public function testCheckTimeoutOnStartedProcess() public function testCheckTimeoutOnStartedProcess()
{ {
$timeout = 0.5; $timeout = 0.5;
@ -672,6 +694,55 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$process->signal(SIGHUP); $process->signal(SIGHUP);
} }
/**
* @dataProvider provideMethodsThatNeedARunningProcess
*/
public function testMethodsThatNeedARunningProcess($method)
{
$process = $this->getProcess('php -m');
$this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
call_user_func(array($process, $method));
}
public function provideMethodsThatNeedARunningProcess()
{
return array(
array('getOutput'),
array('getIncrementalOutput'),
array('getErrorOutput'),
array('getIncrementalErrorOutput'),
array('wait'),
);
}
/**
* @dataProvider provideMethodsThatNeedATerminatedProcess
*/
public function testMethodsThatNeedATerminatedProcess($method)
{
$process = $this->getProcess('php -r "sleep(1);"');
$process->start();
try {
call_user_func(array($process, $method));
$process->stop(0);
$this->fail('A LogicException must have been thrown');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\Component\Process\Exception\LogicException', $e);
$this->assertEquals(sprintf('Process must be terminated before calling %s.', $method), $e->getMessage());
}
$process->stop(0);
}
public function provideMethodsThatNeedATerminatedProcess()
{
return array(
array('hasBeenSignaled'),
array('getTermSignal'),
array('hasBeenStopped'),
array('getStopSignal'),
);
}
private function verifyPosixIsEnabled() private function verifyPosixIsEnabled()
{ {
if (defined('PHP_WINDOWS_VERSION_BUILD')) { if (defined('PHP_WINDOWS_VERSION_BUILD')) {

View File

@ -138,13 +138,13 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
public function testShouldEscapeArguments() public function testShouldEscapeArguments()
{ {
$pb = new ProcessBuilder(array('%path%', 'foo " bar')); $pb = new ProcessBuilder(array('%path%', 'foo " bar', '%baz%baz'));
$proc = $pb->getProcess(); $proc = $pb->getProcess();
if (defined('PHP_WINDOWS_VERSION_BUILD')) { if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->assertSame('^%"path"^% "foo "\\"" bar"', $proc->getCommandLine()); $this->assertSame('^%"path"^% "foo \\" bar" "%baz%baz"', $proc->getCommandLine());
} else { } else {
$this->assertSame("'%path%' 'foo \" bar'", $proc->getCommandLine()); $this->assertSame("'%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine());
} }
} }

View File

@ -27,15 +27,17 @@ class ProcessUtilsTest extends \PHPUnit_Framework_TestCase
{ {
if (defined('PHP_WINDOWS_VERSION_BUILD')) { if (defined('PHP_WINDOWS_VERSION_BUILD')) {
return array( return array(
array('"\"php\" \"-v\""', '"php" "-v"'),
array('"foo bar"', 'foo bar'), array('"foo bar"', 'foo bar'),
array('^%"path"^%', '%path%'), array('^%"path"^%', '%path%'),
array('"<|>"\\"" "\\""\'f"', '<|>" "\'f'), array('"<|>\\" \\"\'f"', '<|>" "\'f'),
array('""', ''), array('""', ''),
array('"with\trailingbs\\\\"', 'with\trailingbs\\'), array('"with\trailingbs\\\\"', 'with\trailingbs\\'),
); );
} }
return array( return array(
array("'\"php\" \"-v\"'", '"php" "-v"'),
array("'foo bar'", 'foo bar'), array("'foo bar'", 'foo bar'),
array("'%path%'", '%path%'), array("'%path%'", '%path%'),
array("'<|>\" \"'\\''f'", '<|>" "\'f'), array("'<|>\" \"'\\''f'", '<|>" "\'f'),

View File

@ -133,6 +133,15 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
$process->getExitCodeText(); $process->getExitCodeText();
} }
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.
*/
public function testExitCodeTextIsNullWhenExitCodeIsNull()
{
parent::testExitCodeTextIsNullWhenExitCodeIsNull();
}
/** /**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException * @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.

View File

@ -112,6 +112,14 @@ class SigchildEnabledProcessTest extends AbstractProcessTest
$this->markTestSkipped('Signal is not supported in sigchild environment'); $this->markTestSkipped('Signal is not supported in sigchild environment');
} }
public function testStartAfterATimeout()
{
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->markTestSkipped('Restarting a timed-out process on Windows is not supported in sigchild environment');
}
parent::testStartAfterATimeout();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -27,106 +27,123 @@ class SimpleProcessTest extends AbstractProcessTest
public function testGetExitCode() public function testGetExitCode()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case
parent::testGetExitCode(); parent::testGetExitCode();
} }
public function testExitCodeCommandFailed() public function testExitCodeCommandFailed()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case
parent::testExitCodeCommandFailed(); parent::testExitCodeCommandFailed();
} }
public function testProcessIsSignaledIfStopped() public function testProcessIsSignaledIfStopped()
{ {
$this->skipIfPHPSigchild(); $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
parent::testProcessIsSignaledIfStopped(); parent::testProcessIsSignaledIfStopped();
} }
public function testProcessWithTermSignal() public function testProcessWithTermSignal()
{ {
$this->skipIfPHPSigchild(); $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
parent::testProcessWithTermSignal(); parent::testProcessWithTermSignal();
} }
public function testProcessIsNotSignaled() public function testProcessIsNotSignaled()
{ {
$this->skipIfPHPSigchild(); $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
parent::testProcessIsNotSignaled(); parent::testProcessIsNotSignaled();
} }
public function testProcessWithoutTermSignal() public function testProcessWithoutTermSignal()
{ {
$this->skipIfPHPSigchild(); $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
parent::testProcessWithoutTermSignal(); parent::testProcessWithoutTermSignal();
} }
public function testExitCodeText() public function testExitCodeText()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case
parent::testExitCodeText(); parent::testExitCodeText();
} }
public function testIsSuccessful() public function testIsSuccessful()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
parent::testIsSuccessful(); parent::testIsSuccessful();
} }
public function testIsNotSuccessful() public function testIsNotSuccessful()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
parent::testIsNotSuccessful(); parent::testIsNotSuccessful();
} }
public function testGetPid() public function testGetPid()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
parent::testGetPid(); parent::testGetPid();
} }
public function testGetPidIsNullBeforeStart() public function testGetPidIsNullBeforeStart()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
parent::testGetPidIsNullBeforeStart(); parent::testGetPidIsNullBeforeStart();
} }
public function testGetPidIsNullAfterRun() public function testGetPidIsNullAfterRun()
{ {
$this->skipIfPHPSigchild(); $this->skipIfPHPSigchild(); // This test use PID that is not available in this case
parent::testGetPidIsNullAfterRun(); parent::testGetPidIsNullAfterRun();
} }
public function testSignal() public function testSignal()
{ {
$this->skipIfPHPSigchild(); $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
parent::testSignal(); parent::testSignal();
} }
/** public function testProcessWithoutTermSignalIsNotSignaled()
* @expectedException \Symfony\Component\Process\Exception\LogicException {
*/ $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved');
parent::testProcessWithoutTermSignalIsNotSignaled();
}
public function testProcessThrowsExceptionWhenExternallySignaled()
{
$this->skipIfPHPSigchild(); // This test use PID that is not available in this case
parent::testProcessThrowsExceptionWhenExternallySignaled();
}
public function testExitCodeIsAvailableAfterSignal()
{
$this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
parent::testExitCodeIsAvailableAfterSignal();
}
public function testSignalProcessNotRunning() public function testSignalProcessNotRunning()
{ {
$this->skipIfPHPSigchild(); $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Can not send signal on a non running process.');
parent::testSignalProcessNotRunning(); parent::testSignalProcessNotRunning();
} }
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignalWithWrongIntSignal() public function testSignalWithWrongIntSignal()
{ {
$this->skipIfPHPSigchild(); if ($this->enabledSigchild) {
$this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
} else {
$this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `-4`.');
}
parent::testSignalWithWrongIntSignal(); parent::testSignalWithWrongIntSignal();
} }
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
*/
public function testSignalWithWrongNonIntSignal() public function testSignalWithWrongNonIntSignal()
{ {
$this->skipIfPHPSigchild(); if ($this->enabledSigchild) {
$this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
} else {
$this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `Céphalopodes`.');
}
parent::testSignalWithWrongNonIntSignal(); parent::testSignalWithWrongNonIntSignal();
} }
@ -144,4 +161,11 @@ class SimpleProcessTest extends AbstractProcessTest
$this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed'); $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed');
} }
} }
private function expectExceptionIfPHPSigchild($classname, $message)
{
if ($this->enabledSigchild) {
$this->setExpectedException($classname, $message);
}
}
} }

View File

@ -18,9 +18,6 @@ use Symfony\Component\Security\Http\HttpUtils;
/** /**
* Class with the default authentication success handling logic. * Class with the default authentication success handling logic.
* *
* Can be optionally be extended from by the developer to alter the behaviour
* while keeping the default behaviour.
*
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com> * @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Alexander <iam.asm89@gmail.com> * @author Alexander <iam.asm89@gmail.com>

View File

@ -68,6 +68,10 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
*/ */
public function decode($data, $format, array $context = array()) public function decode($data, $format, array $context = array())
{ {
if ('' === trim($data)) {
throw new UnexpectedValueException('Invalid XML data, it can not be empty.');
}
$internalErrors = libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true); $disableEntities = libxml_disable_entity_loader(true);
libxml_clear_errors(); libxml_clear_errors();
@ -79,6 +83,8 @@ class XmlEncoder extends SerializerAwareEncoder implements EncoderInterface, Dec
libxml_disable_entity_loader($disableEntities); libxml_disable_entity_loader($disableEntities);
if ($error = libxml_get_last_error()) { if ($error = libxml_get_last_error()) {
libxml_clear_errors();
throw new UnexpectedValueException($error->message); throw new UnexpectedValueException($error->message);
} }

View File

@ -347,6 +347,12 @@ class XmlEncoderTest extends \PHPUnit_Framework_TestCase
} }
} }
public function testDecodeEmptyXml()
{
$this->setExpectedException('Symfony\Component\Serializer\Exception\UnexpectedValueException', 'Invalid XML data, it can not be empty.');
$this->encoder->decode(' ', 'xml');
}
protected function getXmlSource() protected function getXmlSource()
{ {
return '<?xml version="1.0"?>'."\n". return '<?xml version="1.0"?>'."\n".

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Translation\Loader; namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Exception\InvalidResourceException; use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException; use Symfony\Component\Translation\Exception\NotFoundResourceException;
@ -40,12 +41,15 @@ class QtFileLoader implements LoaderInterface
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
} }
$dom = new \DOMDocument(); try {
$current = libxml_use_internal_errors(true); $dom = XmlUtils::loadFile($resource);
if (!@$dom->load($resource, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) { } catch (\InvalidArgumentException $e) {
throw new InvalidResourceException(implode("\n", $this->getXmlErrors())); throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e);
} }
$internalErrors = libxml_use_internal_errors(true);
libxml_clear_errors();
$xpath = new \DOMXPath($dom); $xpath = new \DOMXPath($dom);
$nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]'); $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]');
@ -67,33 +71,8 @@ class QtFileLoader implements LoaderInterface
$catalogue->addResource(new FileResource($resource)); $catalogue->addResource(new FileResource($resource));
} }
libxml_use_internal_errors($current); libxml_use_internal_errors($internalErrors);
return $catalogue; return $catalogue;
} }
/**
* Returns the XML errors of the internal XML parser
*
* @return array An array of errors
*/
private function getXmlErrors()
{
$errors = array();
foreach (libxml_get_errors() as $error) {
$errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
$error->code,
trim($error->message),
$error->file ? $error->file : 'n/a',
$error->line,
$error->column
);
}
libxml_clear_errors();
libxml_use_internal_errors(false);
return $errors;
}
} }

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Translation\Loader; namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Exception\InvalidResourceException; use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException; use Symfony\Component\Translation\Exception\NotFoundResourceException;
@ -86,27 +87,13 @@ class XliffFileLoader implements LoaderInterface
*/ */
private function parseFile($file) private function parseFile($file)
{ {
try {
$dom = XmlUtils::loadFile($file);
} catch (\InvalidArgumentException $e) {
throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $file, $e->getMessage()), $e->getCode(), $e);
}
$internalErrors = libxml_use_internal_errors(true); $internalErrors = libxml_use_internal_errors(true);
$disableEntities = libxml_disable_entity_loader(true);
libxml_clear_errors();
$dom = new \DOMDocument();
$dom->validateOnParse = true;
if (!@$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) {
libxml_disable_entity_loader($disableEntities);
throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors)));
}
libxml_disable_entity_loader($disableEntities);
foreach ($dom->childNodes as $child) {
if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
libxml_use_internal_errors($internalErrors);
throw new InvalidResourceException('Document types are not allowed.');
}
}
$location = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; $location = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd';
$parts = explode('/', $location); $parts = explode('/', $location);

View File

@ -56,4 +56,12 @@ class QtFileLoaderTest extends \PHPUnit_Framework_TestCase
$resource = __DIR__.'/../fixtures/invalid-xml-resources.xlf'; $resource = __DIR__.'/../fixtures/invalid-xml-resources.xlf';
$loader->load($resource, 'en', 'domain1'); $loader->load($resource, 'en', 'domain1');
} }
public function testLoadEmptyResource()
{
$loader = new QtFileLoader();
$resource = __DIR__.'/../fixtures/empty.xlf';
$this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s".', $resource));
$loader->load($resource, 'en', 'domain1');
}
} }

View File

@ -103,4 +103,12 @@ class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
$loader = new XliffFileLoader(); $loader = new XliffFileLoader();
$loader->load(__DIR__.'/../fixtures/withdoctype.xlf', 'en', 'domain1'); $loader->load(__DIR__.'/../fixtures/withdoctype.xlf', 'en', 'domain1');
} }
public function testParseEmptyFile()
{
$loader = new XliffFileLoader();
$resource = __DIR__.'/../fixtures/empty.xlf';
$this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s":', $resource));
$loader->load($resource, 'en', 'domain1');
}
} }

View File

@ -31,7 +31,7 @@ class IbanValidator extends ConstraintValidator
} }
// An IBAN without a country code is not an IBAN. // An IBAN without a country code is not an IBAN.
if (0 === preg_match('/[A-Za-z]/', $value)) { if (0 === preg_match('/[A-Z]/', $value)) {
$this->context->addViolation($constraint->message, array('{{ value }}' => $value)); $this->context->addViolation($constraint->message, array('{{ value }}' => $value));
return; return;
@ -50,7 +50,7 @@ class IbanValidator extends ConstraintValidator
.strval(ord($teststring{1}) - 55) .strval(ord($teststring{1}) - 55)
.substr($teststring, 2, 2); .substr($teststring, 2, 2);
$teststring = preg_replace_callback('/[A-Za-z]/', function ($letter) { $teststring = preg_replace_callback('/[A-Z]/', function ($letter) {
return intval(ord(strtolower($letter[0])) - 87); return intval(ord(strtolower($letter[0])) - 87);
}, $teststring); }, $teststring);

View File

@ -182,6 +182,10 @@ class IbanValidatorTest extends \PHPUnit_Framework_TestCase
array('foo'), array('foo'),
array('123'), array('123'),
array('0750447346'), array('0750447346'),
//Ibans with lower case values are invalid
array('Ae260211000000230064016'),
array('ae260211000000230064016')
); );
} }
} }