Skip to content

Commit d7f8ca7

Browse files
committed
Merge branch '2.8' into 3.1
* 2.8: [Routing] Add missing options in docblock [VarDumper] Fix dumping continuations [HttpFoundation] fixed Request::getContent() reusage bug [Form] Skip CSRF validation on form when POST max size is exceeded Enhance the phpDoc return types so IDEs can handle the configuration tree. fixes Remove 3.0 from branch suggestions for fixes in PR template [Process] Strengthen Windows pipe files opening (again...) Fix #19531 [Form] DateType fails parsing when midnight is not a valid time
2 parents e34426e + 609ee2d commit d7f8ca7

File tree

20 files changed

+191
-59
lines changed

20 files changed

+191
-59
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
| Q | A
22
| ------------- | ---
3-
| Branch? | "master" for new features / 2.7, 2.8, 3.0 or 3.1 for fixes
3+
| Branch? | "master" for new features / 2.7, 2.8 or 3.1 for fixes
44
| Bug fix? | yes/no
55
| New feature? | yes/no
66
| BC breaks? | yes/no

src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<argument>%form.type_extension.csrf.field_name%</argument>
1313
<argument type="service" id="translator.default" />
1414
<argument>%validator.translation_domain%</argument>
15+
<argument type="service" id="form.server_params" />
1516
</service>
1617
</services>
1718
</container>

src/Symfony/Component/Config/Definition/Builder/NodeBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public function variableNode($name)
138138
/**
139139
* Returns the parent node.
140140
*
141-
* @return ParentNodeDefinitionInterface The parent node
141+
* @return ParentNodeDefinitionInterface|NodeDefinition The parent node
142142
*/
143143
public function end()
144144
{

src/Symfony/Component/Config/Definition/Builder/NodeDefinition.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public function attribute($key, $value)
107107
/**
108108
* Returns the parent node.
109109
*
110-
* @return NodeParentInterface|null The builder of the parent node
110+
* @return NodeParentInterface|NodeBuilder|NodeDefinition|null The builder of the parent node
111111
*/
112112
public function end()
113113
{

src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,29 @@ public function reverseTransform($value)
116116
return;
117117
}
118118

119-
$timestamp = $this->getIntlDateFormatter()->parse($value);
119+
// date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due
120+
// to DST changes
121+
$dateOnly = $this->isPatternDateOnly();
122+
123+
$timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value);
120124

121125
if (intl_get_error_code() != 0) {
122126
throw new TransformationFailedException(intl_get_error_message());
123127
}
124128

125129
try {
126-
// read timestamp into DateTime object - the formatter delivers in UTC
127-
$dateTime = new \DateTime(sprintf('@%s', $timestamp));
130+
if ($dateOnly) {
131+
// we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight
132+
return new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone));
133+
}
134+
135+
// read timestamp into DateTime object - the formatter delivers a timestamp
136+
$dateTime = new \DateTime(sprintf('@%s', $timestamp), new \DateTimeZone($this->outputTimezone));
128137
} catch (\Exception $e) {
129138
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
130139
}
131140

132-
if ('UTC' !== $this->inputTimezone) {
141+
if ($this->outputTimezone !== $this->inputTimezone) {
133142
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
134143
}
135144

@@ -139,15 +148,17 @@ public function reverseTransform($value)
139148
/**
140149
* Returns a preconfigured IntlDateFormatter instance.
141150
*
151+
* @param bool $ignoreTimezone Use UTC regardless of the configured timezone.
152+
*
142153
* @return \IntlDateFormatter
143154
*
144155
* @throws TransformationFailedException in case the date formatter can not be constructed.
145156
*/
146-
protected function getIntlDateFormatter()
157+
protected function getIntlDateFormatter($ignoreTimezone = false)
147158
{
148159
$dateFormat = $this->dateFormat;
149160
$timeFormat = $this->timeFormat;
150-
$timezone = $this->outputTimezone;
161+
$timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone;
151162
$calendar = $this->calendar;
152163
$pattern = $this->pattern;
153164

@@ -162,4 +173,24 @@ protected function getIntlDateFormatter()
162173

163174
return $intlDateFormatter;
164175
}
176+
177+
/**
178+
* Checks if the pattern contains only a date.
179+
*
180+
* @param string $pattern The input pattern
181+
*
182+
* @return bool
183+
*/
184+
protected function isPatternDateOnly()
185+
{
186+
if (null === $this->pattern) {
187+
return false;
188+
}
189+
190+
// strip escaped text
191+
$pattern = preg_replace("#'(.*?)'#", '', $this->pattern);
192+
193+
// check for the absence of time-related placeholders
194+
return 0 === preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern);
195+
}
165196
}

src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\Form\FormEvents;
1616
use Symfony\Component\Form\FormError;
1717
use Symfony\Component\Form\FormEvent;
18+
use Symfony\Component\Form\Util\ServerParams;
1819
use Symfony\Component\Security\Csrf\CsrfToken;
1920
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
2021
use Symfony\Component\Translation\TranslatorInterface;
@@ -65,28 +66,35 @@ class CsrfValidationListener implements EventSubscriberInterface
6566
*/
6667
private $translationDomain;
6768

69+
/**
70+
* @var ServerParams
71+
*/
72+
private $serverParams;
73+
6874
public static function getSubscribedEvents()
6975
{
7076
return array(
7177
FormEvents::PRE_SUBMIT => 'preSubmit',
7278
);
7379
}
7480

75-
public function __construct($fieldName, CsrfTokenManagerInterface $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null)
81+
public function __construct($fieldName, CsrfTokenManagerInterface $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null)
7682
{
7783
$this->fieldName = $fieldName;
7884
$this->tokenManager = $tokenManager;
7985
$this->tokenId = $tokenId;
8086
$this->errorMessage = $errorMessage;
8187
$this->translator = $translator;
8288
$this->translationDomain = $translationDomain;
89+
$this->serverParams = $serverParams ?: new ServerParams();
8390
}
8491

8592
public function preSubmit(FormEvent $event)
8693
{
8794
$form = $event->getForm();
95+
$postRequestSizeExceeded = $form->getConfig()->getMethod() === 'POST' && $this->serverParams->hasPostMaxSizeBeenExceeded();
8896

89-
if ($form->isRoot() && $form->getConfig()->getOption('compound')) {
97+
if ($form->isRoot() && $form->getConfig()->getOption('compound') && !$postRequestSizeExceeded) {
9098
$data = $event->getData();
9199

92100
if (!isset($data[$this->fieldName]) || !$this->tokenManager->isTokenValid(new CsrfToken($this->tokenId, $data[$this->fieldName]))) {

src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Form\FormBuilderInterface;
1717
use Symfony\Component\Form\FormView;
1818
use Symfony\Component\Form\FormInterface;
19+
use Symfony\Component\Form\Util\ServerParams;
1920
use Symfony\Component\OptionsResolver\OptionsResolver;
2021
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
2122
use Symfony\Component\Translation\TranslatorInterface;
@@ -50,13 +51,19 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
5051
*/
5152
private $translationDomain;
5253

53-
public function __construct(CsrfTokenManagerInterface $defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null)
54+
/**
55+
* @var ServerParams
56+
*/
57+
private $serverParams;
58+
59+
public function __construct(CsrfTokenManagerInterface $defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null, ServerParams $serverParams = null)
5460
{
5561
$this->defaultTokenManager = $defaultTokenManager;
5662
$this->defaultEnabled = $defaultEnabled;
5763
$this->defaultFieldName = $defaultFieldName;
5864
$this->translator = $translator;
5965
$this->translationDomain = $translationDomain;
66+
$this->serverParams = $serverParams;
6067
}
6168

6269
/**
@@ -78,7 +85,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
7885
$options['csrf_token_id'] ?: ($builder->getName() ?: get_class($builder->getType()->getInnerType())),
7986
$options['csrf_message'],
8087
$this->translator,
81-
$this->translationDomain
88+
$this->translationDomain,
89+
$this->serverParams
8290
))
8391
;
8492
}

src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,7 @@ public function handleRequest(FormInterface $form, $request = null)
7373
// Mark the form with an error if the uploaded size was too large
7474
// This is done here and not in FormValidator because $_POST is
7575
// empty when that error occurs. Hence the form is never submitted.
76-
$contentLength = $this->serverParams->getContentLength();
77-
$maxContentLength = $this->serverParams->getPostMaxSize();
78-
79-
if (!empty($maxContentLength) && $contentLength > $maxContentLength) {
76+
if ($this->serverParams->hasPostMaxSizeBeenExceeded()) {
8077
// Submit the form, but don't clear the default values
8178
$form->submit(null, false);
8279

src/Symfony/Component/Form/NativeRequestHandler.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@ public function handleRequest(FormInterface $form, $request = null)
8181
// Mark the form with an error if the uploaded size was too large
8282
// This is done here and not in FormValidator because $_POST is
8383
// empty when that error occurs. Hence the form is never submitted.
84-
$contentLength = $this->serverParams->getContentLength();
85-
$maxContentLength = $this->serverParams->getPostMaxSize();
86-
87-
if (!empty($maxContentLength) && $contentLength > $maxContentLength) {
84+
if ($this->serverParams->hasPostMaxSizeBeenExceeded()) {
8885
// Submit the form, but don't clear the default values
8986
$form->submit(null, false);
9087

src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,26 @@ public function testReverseTransformWithDifferentPatterns()
227227
$this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06'));
228228
}
229229

230+
public function testReverseTransformDateOnlyWithDstIssue()
231+
{
232+
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'dd/MM/yyyy');
233+
234+
$this->assertDateTimeEquals(
235+
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
236+
$transformer->reverseTransform('28/05/1978')
237+
);
238+
}
239+
240+
public function testReverseTransformDateOnlyWithDstIssueAndEscapedText()
241+
{
242+
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, "'day': dd 'month': MM 'year': yyyy");
243+
244+
$this->assertDateTimeEquals(
245+
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
246+
$transformer->reverseTransform('day: 28 month: 05 year: 1978')
247+
);
248+
}
249+
230250
public function testReverseTransformEmpty()
231251
{
232252
$transformer = new DateTimeToLocalizedStringTransformer();

src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Form\Tests\Extension\Csrf\EventListener;
1313

14+
use Symfony\Component\Form\Form;
1415
use Symfony\Component\Form\FormBuilder;
1516
use Symfony\Component\Form\FormEvent;
1617
use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
@@ -72,4 +73,25 @@ public function testStringFormData()
7273
// Validate accordingly
7374
$this->assertSame($data, $event->getData());
7475
}
76+
77+
public function testMaxPostSizeExceeded()
78+
{
79+
$serverParams = $this
80+
->getMockBuilder('\Symfony\Component\Form\Util\ServerParams')
81+
->disableOriginalConstructor()
82+
->getMock()
83+
;
84+
85+
$serverParams
86+
->expects($this->once())
87+
->method('hasPostMaxSizeBeenExceeded')
88+
->willReturn(true)
89+
;
90+
91+
$event = new FormEvent($this->form, array('csrf' => 'token'));
92+
$validation = new CsrfValidationListener('csrf', $this->tokenManager, 'unknown', 'Error message', null, null, $serverParams);
93+
94+
$validation->preSubmit($event);
95+
$this->assertEmpty($this->form->getErrors());
96+
}
7597
}

src/Symfony/Component/Form/Util/ServerParams.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ public function __construct(RequestStack $requestStack = null)
2525
$this->requestStack = $requestStack;
2626
}
2727

28+
/**
29+
* Returns true if the POST max size has been exceeded in the request.
30+
*
31+
* @return bool
32+
*/
33+
public function hasPostMaxSizeBeenExceeded()
34+
{
35+
$contentLength = $this->getContentLength();
36+
$maxContentLength = $this->getPostMaxSize();
37+
38+
return $maxContentLength && $contentLength > $maxContentLength;
39+
}
40+
2841
/**
2942
* Returns maximum post size in bytes.
3043
*

src/Symfony/Component/HttpFoundation/Request.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1525,7 +1525,7 @@ public function getContent($asResource = false)
15251525
return stream_get_contents($this->content);
15261526
}
15271527

1528-
if (null === $this->content) {
1528+
if (null === $this->content || false === $this->content) {
15291529
$this->content = file_get_contents('php://input');
15301530
}
15311531

src/Symfony/Component/HttpFoundation/Tests/RequestTest.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,8 +1059,16 @@ public function testGetContentCantBeCalledTwiceWithResources($first, $second)
10591059
$req->getContent($second);
10601060
}
10611061

1062+
public function getContentCantBeCalledTwiceWithResourcesProvider()
1063+
{
1064+
return array(
1065+
'Resource then fetch' => array(true, false),
1066+
'Resource then resource' => array(true, true),
1067+
);
1068+
}
1069+
10621070
/**
1063-
* @dataProvider getContentCantBeCalledTwiceWithResourcesProvider
1071+
* @dataProvider getContentCanBeCalledTwiceWithResourcesProvider
10641072
* @requires PHP 5.6
10651073
*/
10661074
public function testGetContentCanBeCalledTwiceWithResources($first, $second)
@@ -1077,12 +1085,14 @@ public function testGetContentCanBeCalledTwiceWithResources($first, $second)
10771085
$b = stream_get_contents($b);
10781086
}
10791087

1080-
$this->assertEquals($a, $b);
1088+
$this->assertSame($a, $b);
10811089
}
10821090

1083-
public function getContentCantBeCalledTwiceWithResourcesProvider()
1091+
public function getContentCanBeCalledTwiceWithResourcesProvider()
10841092
{
10851093
return array(
1094+
'Fetch then fetch' => array(false, false),
1095+
'Fetch then resource' => array(false, true),
10861096
'Resource then fetch' => array(true, false),
10871097
'Resource then resource' => array(true, true),
10881098
);

src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,7 @@ public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1)
201201

202202
foreach ($this->data as $dump) {
203203
$dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth));
204-
205-
rewind($data);
206-
$dump['data'] = stream_get_contents($data);
204+
$dump['data'] = stream_get_contents($data, -1, 0);
207205
ftruncate($data, 0);
208206
rewind($data);
209207
$dumps[] = $dump;

src/Symfony/Component/Process/Pipes/WindowsPipes.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,22 @@ public function __construct($input, $haveReadSupport)
5151
Process::STDOUT => Process::OUT,
5252
Process::STDERR => Process::ERR,
5353
);
54+
$tmpCheck = false;
5455
$tmpDir = sys_get_temp_dir();
55-
$error = 'unknown reason';
56-
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
56+
$lastError = 'unknown reason';
57+
set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
5758
for ($i = 0;; ++$i) {
5859
foreach ($pipes as $pipe => $name) {
5960
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
6061
if (file_exists($file) && !unlink($file)) {
6162
continue 2;
6263
}
6364
$h = fopen($file, 'xb');
64-
if (!$h && false === strpos($error, 'File exists')) {
65+
if (!$h) {
66+
$error = $lastError;
67+
if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) {
68+
continue;
69+
}
6570
restore_error_handler();
6671
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error));
6772
}

0 commit comments

Comments
 (0)