From ebf967de8f9cc2fe8ec10b9fa1243f5bfdf0385f Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Sun, 6 Jul 2014 15:41:58 +0200 Subject: [PATCH 01/25] [Form] Check if IntlDateFormatter constructor returned a valid object before using it --- .../DateTimeToLocalizedStringTransformer.php | 8 ++++++++ .../Component/Form/Extension/Core/Type/DateType.php | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index 56a3ca928b..e992b01fc1 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -152,6 +152,8 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer * Returns a preconfigured IntlDateFormatter instance * * @return \IntlDateFormatter + * + * @throws TransformationFailedException in case the date formatter can not be constructed. */ protected function getIntlDateFormatter() { @@ -162,6 +164,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer $pattern = $this->pattern; $intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern); + + // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323 + if (!$intlDateFormatter) { + throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code()); + } + $intlDateFormatter->setLenient(false); return $intlDateFormatter; diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 022a414932..1748d2e31f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -77,6 +77,12 @@ class DateType extends AbstractType $calendar, $pattern ); + + // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323 + if (!$formatter) { + throw new InvalidOptionsException(intl_get_error_message(), intl_get_error_code()); + } + $formatter->setLenient(false); if ('choice' === $options['widget']) { From 6af3d05b851161a42d29b1c032f30611c0b873ce Mon Sep 17 00:00:00 2001 From: Andrew Moore Date: Thu, 10 Jul 2014 09:27:11 -0400 Subject: [PATCH 02/25] [HttpFoundation] Fix to prevent magic bytes injection in JSONP responses (Prevents CVE-2014-4671) --- src/Symfony/Component/HttpFoundation/JsonResponse.php | 2 +- src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php index 15e7b582f7..83a607cd6e 100644 --- a/src/Symfony/Component/HttpFoundation/JsonResponse.php +++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php @@ -111,7 +111,7 @@ class JsonResponse extends Response // Not using application/javascript for compatibility reasons with older browsers. $this->headers->set('Content-Type', 'text/javascript'); - return $this->setContent(sprintf('%s(%s);', $this->callback, $this->data)); + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); } // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) diff --git a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php index ef392ca59d..2cb6b68550 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php @@ -155,7 +155,7 @@ class JsonResponseTest extends \PHPUnit_Framework_TestCase { $response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback'); - $this->assertEquals('callback({"foo":"bar"});', $response->getContent()); + $this->assertEquals('/**/callback({"foo":"bar"});', $response->getContent()); $this->assertEquals('text/javascript', $response->headers->get('Content-Type')); } From 9e1ea4aa4b21db1040d6e92a2b6dfa0bf5ad672a Mon Sep 17 00:00:00 2001 From: Manatsawin Hanmongkolchai Date: Tue, 1 Jul 2014 20:28:47 +0700 Subject: [PATCH 03/25] [Process] Use correct test for empty string in UnixPipes --- src/Symfony/Component/Process/ProcessPipes.php | 4 ++-- src/Symfony/Component/Process/Tests/AbstractProcessTest.php | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Process/ProcessPipes.php b/src/Symfony/Component/Process/ProcessPipes.php index f35d1c1287..850741e6a8 100644 --- a/src/Symfony/Component/Process/ProcessPipes.php +++ b/src/Symfony/Component/Process/ProcessPipes.php @@ -313,11 +313,11 @@ class ProcessPipes $type = array_search($pipe, $this->pipes); $data = ''; - while ($dataread = fread($pipe, self::CHUNK_SIZE)) { + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { $data .= $dataread; } - if ($data) { + if ('' !== $data) { $read[$type] = $data; } diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index 91b8aca20d..3074874743 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -287,6 +287,12 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase } } + public function testZeroAsOutput(){ + $p = $this->getProcess('printf 0'); + $p->run(); + $this->assertSame('0', $p->getOutput()); + } + public function testExitCodeCommandFailed() { if (defined('PHP_WINDOWS_VERSION_BUILD')) { From 8f9ed3ebb9ef126cc8940da493f252619671c0f3 Mon Sep 17 00:00:00 2001 From: Christopher Davis Date: Sun, 13 Jul 2014 13:09:52 -0400 Subject: [PATCH 04/25] Remove Spaceless Blocks from Twig Form Templates In favor of using Twig's whitespace control operators. See #11277 --- .../views/Form/form_div_layout.html.twig | 418 +++++++----------- .../views/Form/form_table_layout.html.twig | 44 +- 2 files changed, 187 insertions(+), 275 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 9440658cbe..c14031a713 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -1,393 +1,313 @@ {# Widgets #} -{% block form_widget %} -{% spaceless %} +{% block form_widget -%} {% if compound %} - {{ block('form_widget_compound') }} + {{- block('form_widget_compound') -}} {% else %} - {{ block('form_widget_simple') }} + {{- block('form_widget_simple') -}} {% endif %} -{% endspaceless %} -{% endblock form_widget %} +{%- endblock form_widget %} -{% block form_widget_simple %} -{% spaceless %} - {% set type = type|default('text') %} +{% block form_widget_simple -%} + {%- set type = type|default('text') -%} -{% endspaceless %} -{% endblock form_widget_simple %} +{%- endblock form_widget_simple %} -{% block form_widget_compound %} -{% spaceless %} +{% block form_widget_compound -%}
- {% if form.parent is empty %} + {%- if form.parent is empty -%} {{ form_errors(form) }} - {% endif %} - {{ block('form_rows') }} - {{ form_rest(form) }} + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}}
-{% endspaceless %} -{% endblock form_widget_compound %} +{%- endblock form_widget_compound %} -{% block collection_widget %} -{% spaceless %} +{% block collection_widget -%} {% if prototype is defined %} - {% set attr = attr|merge({'data-prototype': form_row(prototype) }) %} + {%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%} {% endif %} - {{ block('form_widget') }} -{% endspaceless %} -{% endblock collection_widget %} + {{- block('form_widget') -}} +{%- endblock collection_widget %} -{% block textarea_widget %} -{% spaceless %} +{% block textarea_widget -%} -{% endspaceless %} -{% endblock textarea_widget %} +{%- endblock textarea_widget %} -{% block choice_widget %} -{% spaceless %} +{% block choice_widget -%} {% if expanded %} - {{ block('choice_widget_expanded') }} + {{- block('choice_widget_expanded') -}} {% else %} - {{ block('choice_widget_collapsed') }} + {{- block('choice_widget_collapsed') -}} {% endif %} -{% endspaceless %} -{% endblock choice_widget %} +{%- endblock choice_widget %} -{% block choice_widget_expanded %} -{% spaceless %} +{% block choice_widget_expanded -%}
{% for child in form %} - {{ form_widget(child) }} - {{ form_label(child) }} + {{- form_widget(child) -}} + {{- form_label(child) -}} {% endfor %}
-{% endspaceless %} -{% endblock choice_widget_expanded %} +{%- endblock choice_widget_expanded %} -{% block choice_widget_collapsed %} -{% spaceless %} +{% block choice_widget_collapsed -%} {% if required and empty_value is none and not empty_value_in_choices and not multiple %} {% set required = false %} {% endif %} -{% endspaceless %} -{% endblock choice_widget_collapsed %} +{%- endblock choice_widget_collapsed %} -{% block choice_widget_options %} -{% spaceless %} +{% block choice_widget_options -%} {% for group_label, choice in options %} - {% if choice is iterable %} + {%- if choice is iterable -%} {% set options = choice %} - {{ block('choice_widget_options') }} + {{- block('choice_widget_options') -}} - {% else %} + {%- else -%} - {% endif %} + {%- endif -%} {% endfor %} -{% endspaceless %} -{% endblock choice_widget_options %} +{%- endblock choice_widget_options %} -{% block checkbox_widget %} -{% spaceless %} +{% block checkbox_widget -%} -{% endspaceless %} -{% endblock checkbox_widget %} +{%- endblock checkbox_widget %} -{% block radio_widget %} -{% spaceless %} +{% block radio_widget -%} -{% endspaceless %} -{% endblock radio_widget %} +{%- endblock radio_widget %} -{% block datetime_widget %} -{% spaceless %} +{% block datetime_widget -%} {% if widget == 'single_text' %} - {{ block('form_widget_simple') }} - {% else %} + {{- block('form_widget_simple') -}} + {% else -%}
- {{ form_errors(form.date) }} - {{ form_errors(form.time) }} - {{ form_widget(form.date) }} - {{ form_widget(form.time) }} + {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + {{- form_widget(form.date) -}} + {{- form_widget(form.time) -}}
- {% endif %} -{% endspaceless %} -{% endblock datetime_widget %} + {%- endif %} +{%- endblock datetime_widget %} -{% block date_widget %} -{% spaceless %} - {% if widget == 'single_text' %} +{% block date_widget -%} + {% if widget == 'single_text' -%} {{ block('form_widget_simple') }} - {% else %} + {%- else -%}
- {{ date_pattern|replace({ + {{- date_pattern|replace({ '{{ year }}': form_widget(form.year), '{{ month }}': form_widget(form.month), '{{ day }}': form_widget(form.day), - })|raw }} + })|raw -}}
- {% endif %} -{% endspaceless %} -{% endblock date_widget %} + {%- endif %} +{%- endblock date_widget %} -{% block time_widget %} -{% spaceless %} - {% if widget == 'single_text' %} +{% block time_widget -%} + {% if widget == 'single_text' -%} {{ block('form_widget_simple') }} - {% else %} - {% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} %} + {%- else -%} + {% set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%}
{{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %}
- {% endif %} -{% endspaceless %} -{% endblock time_widget %} + {%- endif %} +{%- endblock time_widget %} -{% block number_widget %} -{% spaceless %} +{% block number_widget -%} {# type="number" doesn't work with floats #} - {% set type = type|default('text') %} + {%- set type = type|default('text') -%} {{ block('form_widget_simple') }} -{% endspaceless %} -{% endblock number_widget %} +{%- endblock number_widget %} -{% block integer_widget %} -{% spaceless %} +{% block integer_widget -%} {% set type = type|default('number') %} - {{ block('form_widget_simple') }} -{% endspaceless %} -{% endblock integer_widget %} + {{- block('form_widget_simple') -}} +{%- endblock integer_widget %} -{% block money_widget %} -{% spaceless %} +{% block money_widget -%} {{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }} -{% endspaceless %} -{% endblock money_widget %} +{%- endblock money_widget %} -{% block url_widget %} -{% spaceless %} - {% set type = type|default('url') %} +{% block url_widget -%} + {% set type = type|default('url') -%} {{ block('form_widget_simple') }} -{% endspaceless %} -{% endblock url_widget %} +{%- endblock url_widget %} -{% block search_widget %} -{% spaceless %} - {% set type = type|default('search') %} +{% block search_widget -%} + {% set type = type|default('search') -%} {{ block('form_widget_simple') }} -{% endspaceless %} -{% endblock search_widget %} +{%- endblock search_widget %} -{% block percent_widget %} -{% spaceless %} - {% set type = type|default('text') %} +{% block percent_widget -%} + {% set type = type|default('text') -%} {{ block('form_widget_simple') }} % -{% endspaceless %} -{% endblock percent_widget %} +{%- endblock percent_widget %} -{% block password_widget %} -{% spaceless %} - {% set type = type|default('password') %} +{% block password_widget -%} + {% set type = type|default('password') -%} {{ block('form_widget_simple') }} -{% endspaceless %} -{% endblock password_widget %} +{%- endblock password_widget %} -{% block hidden_widget %} -{% spaceless %} - {% set type = type|default('hidden') %} +{% block hidden_widget -%} + {% set type = type|default('hidden') -%} {{ block('form_widget_simple') }} -{% endspaceless %} -{% endblock hidden_widget %} +{%- endblock hidden_widget %} -{% block email_widget %} -{% spaceless %} - {% set type = type|default('email') %} +{% block email_widget -%} + {% set type = type|default('email') -%} {{ block('form_widget_simple') }} -{% endspaceless %} -{% endblock email_widget %} +{%- endblock email_widget %} -{% block button_widget %} -{% spaceless %} - {% if label is empty %} +{% block button_widget -%} + {% if label is empty -%} {% set label = name|humanize %} - {% endif %} + {%- endif -%} -{% endspaceless %} -{% endblock button_widget %} +{%- endblock button_widget %} -{% block submit_widget %} -{% spaceless %} - {% set type = type|default('submit') %} +{% block submit_widget -%} + {% set type = type|default('submit') -%} {{ block('button_widget') }} -{% endspaceless %} -{% endblock submit_widget %} +{%- endblock submit_widget %} -{% block reset_widget %} -{% spaceless %} - {% set type = type|default('reset') %} +{% block reset_widget -%} + {% set type = type|default('reset') -%} {{ block('button_widget') }} -{% endspaceless %} -{% endblock reset_widget %} +{%- endblock reset_widget %} {# Labels #} -{% block form_label %} -{% spaceless %} +{% block form_label -%} {% if label is not sameas(false) %} - {% if not compound %} + {%- if not compound -%} {% set label_attr = label_attr|merge({'for': id}) %} - {% endif %} - {% if required %} + {%- endif -%} + {%- if required -%} {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} - {% endif %} - {% if label is empty %} + {%- endif -%} + {%- if label is empty -%} {% set label = name|humanize %} - {% endif %} + {%- endif -%} {{ label|trans({}, translation_domain) }} - {% endif %} -{% endspaceless %} -{% endblock form_label %} + {%- endif %} +{%- endblock form_label %} -{% block button_label %}{% endblock %} +{% block button_label -%}{%- endblock %} {# Rows #} -{% block repeated_row %} -{% spaceless %} +{% block repeated_row -%} {# No need to render the errors here, as all errors are mapped to the first child (see RepeatedTypeValidatorExtension). #} - {{ block('form_rows') }} -{% endspaceless %} -{% endblock repeated_row %} + {{- block('form_rows') }} +{%- endblock repeated_row %} -{% block form_row %} -{% spaceless %} +{% block form_row -%}
- {{ form_label(form) }} - {{ form_errors(form) }} - {{ form_widget(form) }} + {{- form_label(form) -}} + {{- form_errors(form) -}} + {{- form_widget(form) -}}
-{% endspaceless %} -{% endblock form_row %} +{%- endblock form_row %} -{% block button_row %} -{% spaceless %} +{% block button_row -%}
- {{ form_widget(form) }} + {{- form_widget(form) -}}
-{% endspaceless %} -{% endblock button_row %} +{%- endblock button_row %} -{% block hidden_row %} +{% block hidden_row -%} {{ form_widget(form) }} -{% endblock hidden_row %} +{%- endblock hidden_row %} {# Misc #} -{% block form %} -{% spaceless %} +{% block form -%} {{ form_start(form) }} - {{ form_widget(form) }} + {{- form_widget(form) -}} {{ form_end(form) }} -{% endspaceless %} -{% endblock form %} +{%- endblock form %} -{% block form_start %} -{% spaceless %} +{% block form_start -%} {% set method = method|upper %} - {% if method in ["GET", "POST"] %} + {%- if method in ["GET", "POST"] -%} {% set form_method = method %} - {% else %} + {%- else -%} {% set form_method = "POST" %} - {% endif %} + {%- endif -%}
- {% if form_method != method %} + {%- if form_method != method -%} - {% endif %} -{% endspaceless %} -{% endblock form_start %} + {%- endif %} +{%- endblock form_start %} -{% block form_end %} -{% spaceless %} - {% if not render_rest is defined or render_rest %} +{% block form_end -%} + {% if not render_rest is defined or render_rest -%} {{ form_rest(form) }} - {% endif %} + {%- endif -%}
-{% endspaceless %} -{% endblock form_end %} +{%- endblock form_end %} -{% block form_enctype %} -{% spaceless %} +{% block form_enctype -%} {% if multipart %}enctype="multipart/form-data"{% endif %} -{% endspaceless %} -{% endblock form_enctype %} +{%- endblock form_enctype %} -{% block form_errors %} -{% spaceless %} - {% if errors|length > 0 %} +{% block form_errors -%} + {% if errors|length > 0 -%}
    - {% for error in errors %} + {%- for error in errors -%}
  • {{ error.message }}
  • - {% endfor %} + {%- endfor -%}
- {% endif %} -{% endspaceless %} -{% endblock form_errors %} + {%- endif %} +{%- endblock form_errors %} -{% block form_rest %} -{% spaceless %} - {% for child in form %} - {% if not child.rendered %} +{% block form_rest -%} + {% for child in form -%} + {% if not child.rendered -%} {{ form_row(child) }} - {% endif %} - {% endfor %} -{% endspaceless %} -{% endblock form_rest %} + {%- endif %} + {%- endfor %} +{%- endblock form_rest %} {# Support #} -{% block form_rows %} -{% spaceless %} - {% for child in form %} +{% block form_rows -%} + {% for child in form -%} {{ form_row(child) }} - {% endfor %} -{% endspaceless %} -{% endblock form_rows %} + {%- endfor %} +{%- endblock form_rows %} -{% block widget_attributes %} -{% spaceless %} +{% block widget_attributes -%} id="{{ id }}" name="{{ full_name }}"{% if read_only %} readonly="readonly"{% endif %}{% if disabled %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %} {% for attrname, attrvalue in attr %}{% if attrname in ['placeholder', 'title'] %}{{ attrname }}="{{ attrvalue|trans({}, translation_domain) }}" {% else %}{{ attrname }}="{{ attrvalue }}" {% endif %}{% endfor %} -{% endspaceless %} -{% endblock widget_attributes %} +{%- endblock widget_attributes %} -{% block widget_container_attributes %} -{% spaceless %} +{% block widget_container_attributes -%} {% if id is not empty %}id="{{ id }}" {% endif %} - {% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} -{% endspaceless %} -{% endblock widget_container_attributes %} + {%- for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} +{%- endblock widget_container_attributes %} -{% block button_attributes %} -{% spaceless %} +{% block button_attributes -%} id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif %} - {% for attrname, attrvalue in attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %} -{% endspaceless %} -{% endblock button_attributes %} + {%- for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %} +{%- endblock button_attributes %} diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig index aed4f8d770..d3d7a34f40 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_table_layout.html.twig @@ -1,52 +1,44 @@ {% use "form_div_layout.html.twig" %} -{% block form_row %} -{% spaceless %} +{% block form_row -%} - {{ form_label(form) }} + {{- form_label(form) -}} - {{ form_errors(form) }} - {{ form_widget(form) }} + {{- form_errors(form) -}} + {{- form_widget(form) -}} -{% endspaceless %} -{% endblock form_row %} +{%- endblock form_row %} -{% block button_row %} -{% spaceless %} +{% block button_row -%} - {{ form_widget(form) }} + {{- form_widget(form) -}} -{% endspaceless %} -{% endblock button_row %} +{%- endblock button_row %} -{% block hidden_row %} -{% spaceless %} +{% block hidden_row -%} - {{ form_widget(form) }} + {{- form_widget(form) -}} -{% endspaceless %} -{% endblock hidden_row %} +{%- endblock hidden_row %} -{% block form_widget_compound %} -{% spaceless %} +{% block form_widget_compound -%} - {% if form.parent is empty and errors|length > 0 %} + {% if form.parent is empty and errors|length > 0 -%} - {% endif %} - {{ block('form_rows') }} - {{ form_rest(form) }} + {%- endif %} + {{- block('form_rows') -}} + {{- form_rest(form) -}}
- {{ form_errors(form) }} + {{- form_errors(form) -}}
-{% endspaceless %} -{% endblock form_widget_compound %} +{%- endblock form_widget_compound %} From 73d50edc17928d8bf3fb99dc8d964d675d2c240c Mon Sep 17 00:00:00 2001 From: redstar504 Date: Sun, 13 Jul 2014 22:59:18 -0700 Subject: [PATCH 05/25] Fix UserPassword validator translation --- .../Validator/Resources/translations/validators.en.xlf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf index 3b8b5d97a8..756b80b37f 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.en.xlf @@ -176,7 +176,7 @@ This value should be the user current password. - This value should be the user current password. + This value should be the user's current password. This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. From 06a80fbdbe744ad6f3010479ba64ef5cf35dd9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Fri, 4 Jul 2014 19:20:43 +0200 Subject: [PATCH 06/25] Validate locales sets intos translator --- .../Tests/Translation/TranslatorTest.php | 17 +- .../Translation/Translator.php | 6 +- .../Translation/Tests/TranslatorTest.php | 175 ++++++++++++++++++ .../Component/Translation/Translator.php | 35 +++- .../Translation/TranslatorInterface.php | 6 + 5 files changed, 233 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index 614f6dbc2d..715c44fade 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -45,7 +45,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase { $translator = $this->getTranslator($this->getLoader()); $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR')); + $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); $this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('bar (EN)', $translator->trans('bar')); @@ -54,6 +54,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('no translation', $translator->trans('no translation')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); + $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); } public function testTransWithCaching() @@ -61,7 +62,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase // prime the cache $translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir)); $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR')); + $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); $this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('bar (EN)', $translator->trans('bar')); @@ -70,12 +71,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('no translation', $translator->trans('no translation')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); + $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); // do it another time as the cache is primed now $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir)); $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR')); + $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); $this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('bar (EN)', $translator->trans('bar')); @@ -84,6 +86,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('no translation', $translator->trans('no translation')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); + $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); } public function testGetLocale() @@ -175,6 +178,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase 'other choice' => '{0} other choice 0 (PT-BR)|{1} other choice 1 (PT-BR)|]1,Inf] other choice inf (PT-BR)', )))) ; + $loader + ->expects($this->at(5)) + ->method('load') + ->will($this->returnValue($this->getCatalogue('fr.UTF-8', array( + 'foobarbaz' => 'foobarbaz (fr.UTF-8)', + )))) + ; return $loader; } @@ -205,6 +215,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $translator->addResource('loader', 'foo', 'es'); $translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese $translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese + $translator->addResource('loader', 'foo', 'fr.UTF-8'); return $translator; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index 1705c1ac06..0f99f6428e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -97,8 +97,10 @@ class Translator extends BaseTranslator $fallbackContent = ''; $current = ''; + $replacementPattern = '/[^a-z0-9_]/i'; foreach ($this->computeFallbackLocales($locale) as $fallback) { - $fallbackSuffix = ucfirst(str_replace('-', '_', $fallback)); + $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); + $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); $fallbackContent .= sprintf(<<catalogues[$fallback]->all(), true), - ucfirst(str_replace('-', '_', $current)), + $currentSuffix, $fallbackSuffix ); $current = $fallback; diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index e45ab3b481..40a0b1d0d9 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -17,6 +17,33 @@ use Symfony\Component\Translation\Loader\ArrayLoader; class TranslatorTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testConstructorInvalidLocale($locale) + { + $translator = new Translator($locale, new MessageSelector()); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testConstructorValidLocale($locale) + { + $translator = new Translator($locale, new MessageSelector()); + + $this->assertEquals($locale, $translator->getLocale()); + } + + public function testConstructorWithoutLocale() + { + $translator = new Translator(null, new MessageSelector()); + + $this->assertNull($translator->getLocale()); + } + public function testSetGetLocale() { $translator = new Translator('en', new MessageSelector()); @@ -27,6 +54,27 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('fr', $translator->getLocale()); } + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testSetInvalidLocale($locale) + { + $translator = new Translator('fr', new MessageSelector()); + $translator->setLocale($locale); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testSetValidLocale($locale) + { + $translator = new Translator($locale, new MessageSelector()); + $translator->setLocale($locale); + + $this->assertEquals($locale, $translator->getLocale()); + } + public function testSetFallbackLocales() { $translator = new Translator('en', new MessageSelector()); @@ -55,6 +103,27 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('bar (fr)', $translator->trans('bar')); } + + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testSetFallbackInvalidLocales($locale) + { + $translator = new Translator('fr', new MessageSelector()); + $translator->setFallbackLocales(array('fr', $locale)); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testSetFallbackValidLocales($locale) + { + $translator = new Translator($locale, new MessageSelector()); + $translator->setFallbackLocales(array('fr', $locale)); + // no assertion. this method just asserts that no exception is thrown + } + public function testTransWithFallbackLocale() { $translator = new Translator('fr_FR', new MessageSelector()); @@ -67,6 +136,26 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foobar', $translator->trans('bar')); } + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testAddResourceInvalidLocales($locale) + { + $translator = new Translator('fr', new MessageSelector()); + $translator->addResource('array', array('foo' => 'foofoo'), $locale); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testAddResourceValidLocales($locale) + { + $translator = new Translator('fr', new MessageSelector()); + $translator->addResource('array', array('foo' => 'foofoo'), $locale); + // no assertion. this method just asserts that no exception is thrown + } + public function testAddResourceAfterTrans() { $translator = new Translator('fr', new MessageSelector()); @@ -164,6 +253,32 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale)); } + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testTransInvalidLocale($locale) + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + + $translator->trans('foo', array(), '', $locale); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testTransValidLocale($locale) + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + + $translator->trans('foo', array(), '', $locale); + // no assertion. this method just asserts that no exception is thrown + } + /** * @dataProvider getFlattenedTransTests */ @@ -188,6 +303,33 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale)); } + /** + * @dataProvider getInvalidLocalesTests + * @expectedException \InvalidArgumentException + */ + public function testTransChoiceInvalidLocale($locale) + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + + $translator->transChoice('foo', 1, array(), '', $locale); + } + + /** + * @dataProvider getValidLocalesTests + */ + public function testTransChoiceValidLocale($locale) + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + + $translator->transChoice('foo', 1, array(), '', $locale); + // no assertion. this method just asserts that no exception is thrown + } + + public function getTransFileTests() { return array( @@ -257,6 +399,39 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase ); } + public function getInvalidLocalesTests() + { + return array( + array('fr FR'), + array('français'), + array('fr+en'), + array('utf#8'), + array('fr&en'), + array('fr~FR'), + array(' fr'), + array('fr '), + array('fr*'), + array('fr/FR'), + array('fr\\FR'), + ); + } + + public function getValidLocalesTests() + { + return array( + array(''), + array(null), + array('fr'), + array('francais'), + array('FR'), + array('frFR'), + array('fr-FR'), + array('fr_FR'), + array('fr.FR'), + array('fr-FR.UTF8'), + ); + } + public function testTransChoiceFallback() { $translator = new Translator('ru', new MessageSelector()); diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 8e74b79f60..e54b300ffd 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -59,11 +59,13 @@ class Translator implements TranslatorInterface * @param string $locale The locale * @param MessageSelector|null $selector The message selector for pluralization * + * @throws \InvalidArgumentException If a locale contains invalid characters + * * @api */ public function __construct($locale, MessageSelector $selector = null) { - $this->locale = $locale; + $this->setLocale($locale); $this->selector = $selector ?: new MessageSelector(); } @@ -88,6 +90,8 @@ class Translator implements TranslatorInterface * @param string $locale The locale * @param string $domain The domain * + * @throws \InvalidArgumentException If the locale contains invalid characters + * * @api */ public function addResource($format, $resource, $locale, $domain = null) @@ -96,6 +100,8 @@ class Translator implements TranslatorInterface $domain = 'messages'; } + $this->assertValidLocale($locale); + $this->resources[$locale][] = array($format, $resource, $domain); if (in_array($locale, $this->fallbackLocales)) { @@ -112,6 +118,7 @@ class Translator implements TranslatorInterface */ public function setLocale($locale) { + $this->assertValidLocale($locale); $this->locale = $locale; } @@ -130,6 +137,8 @@ class Translator implements TranslatorInterface * * @param string|array $locales The fallback locale(s) * + * @throws \InvalidArgumentException If a locale contains invalid characters + * * @deprecated since 2.3, to be removed in 3.0. Use setFallbackLocales() instead. * * @api @@ -144,6 +153,8 @@ class Translator implements TranslatorInterface * * @param array $locales The fallback locales * + * @throws \InvalidArgumentException If a locale contains invalid characters + * * @api */ public function setFallbackLocales(array $locales) @@ -151,6 +162,10 @@ class Translator implements TranslatorInterface // needed as the fallback locales are linked to the already loaded catalogues $this->catalogues = array(); + foreach ($locales as $locale) { + $this->assertValidLocale($locale); + } + $this->fallbackLocales = $locales; } @@ -175,6 +190,8 @@ class Translator implements TranslatorInterface { if (null === $locale) { $locale = $this->getLocale(); + } else { + $this->assertValidLocale($locale); } if (null === $domain) { @@ -197,6 +214,8 @@ class Translator implements TranslatorInterface { if (null === $locale) { $locale = $this->getLocale(); + } else { + $this->assertValidLocale($locale); } if (null === $domain) { @@ -279,4 +298,18 @@ class Translator implements TranslatorInterface return array_unique($locales); } + + /** + * Asserts that the locale is valid, throws an Exception if not. + * + * @param string $locale Locale to tests + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + private function assertValidLocale($locale) + { + if (0 !== preg_match('/[^a-z0-9_\\.\\-]+/i', $locale, $match)) { + throw new \InvalidArgumentException(sprintf('Invalid locale: %s.', $locale)); + } + } } diff --git a/src/Symfony/Component/Translation/TranslatorInterface.php b/src/Symfony/Component/Translation/TranslatorInterface.php index a97fd10b8a..436d48872e 100644 --- a/src/Symfony/Component/Translation/TranslatorInterface.php +++ b/src/Symfony/Component/Translation/TranslatorInterface.php @@ -28,6 +28,8 @@ interface TranslatorInterface * @param string $domain The domain for the message * @param string $locale The locale * + * @throws \InvalidArgumentException If the locale contains invalid characters + * * @return string The translated string * * @api @@ -43,6 +45,8 @@ interface TranslatorInterface * @param string $domain The domain for the message * @param string $locale The locale * + * @throws \InvalidArgumentException If the locale contains invalid characters + * * @return string The translated string * * @api @@ -54,6 +58,8 @@ interface TranslatorInterface * * @param string $locale The locale * + * @throws \InvalidArgumentException If the locale contains invalid characters + * * @api */ public function setLocale($locale); From 98b891d27127333d10df0893f281f6055518bf27 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 15 Jul 2014 16:18:51 +0200 Subject: [PATCH 07/25] updated CHANGELOG for 2.3.18 --- CHANGELOG-2.3.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG-2.3.md b/CHANGELOG-2.3.md index 9b0b1f994e..c132013aa0 100644 --- a/CHANGELOG-2.3.md +++ b/CHANGELOG-2.3.md @@ -7,6 +7,14 @@ in 2.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v2.3.0...v2.3.1 +* 2.3.18 (2014-07-15) + + * [Security] Forced validate of locales passed to the translator + * feature #11367 [HttpFoundation] Fix to prevent magic bytes injection in JSONP responses... (CVE-2014-4671) (Andrew Moore) + * bug #11386 Remove Spaceless Blocks from Twig Form Templates (chrisguitarguy) + * bug #9719 [TwigBundle] fix configuration tree for paths (mdavis1982, cordoval) + * bug #11244 [HttpFoundation] Remove body-related headers when sending the response, if body is empty (SimonSimCity) + * 2.3.17 (2014-07-07) * bug #11238 [Translation] Added unescaping of ids in PoFileLoader (JustBlackBird) From 4a12f4d0f2552d877bab448745b07c9a842b791a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 15 Jul 2014 16:20:27 +0200 Subject: [PATCH 08/25] update CONTRIBUTORS for 2.3.18 --- CONTRIBUTORS.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 007ceffdf4..ca9e441c93 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -19,8 +19,8 @@ Symfony2 is the result of the work of many people who made the code better - Ryan Weaver (weaverryan) - Lukas Kahwe Smith (lsmith) - Jeremy Mikola (jmikola) - - Jean-François Simon (jfsimon) - Romain Neutron (romain) + - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - Hugo Hamon (hhamon) @@ -44,18 +44,18 @@ Symfony2 is the result of the work of many people who made the code better - Wouter De Jong (wouterj) - Eric Clemmons (ericclemmons) - Nicolas Grekas (nicolas-grekas) + - Andrej Hudec (pulzarraider) - Deni - Henrik Westphal (snc) - Dariusz Górecki (canni) - - Andrej Hudec (pulzarraider) - Arnout Boks (aboks) + - Christian Raue - Michel Weimerskirch (mweimerskirch) - Lee McDermott - Brandon Turner - Douglas Greenshields (shieldo) - Daniel Holmes (dholmes) - Jordan Alliot (jalliot) - - Christian Raue - John Wards (johnwards) - Fran Moreno (franmomu) - Bart van den Burg (burgov) @@ -75,21 +75,21 @@ Symfony2 is the result of the work of many people who made the code better - Fabien Pennequin (fabienpennequin) - Jacob Dreesen (jdreesen) - Gábor Egyed (1ed) + - Ait Boudad Abdellatif (aitboudad) - Adrien Brault (adrienbrault) - Michal Piotrowski (eventhorizon) - - Ait Boudad Abdellatif (aitboudad) - Robert Schönthal (digitalkaoz) - Juti Noppornpitak (shiroyuki) - Sebastian Hörl (blogsh) - Daniel Gomes (danielcsgomes) - Hidenori Goto (hidenorigoto) - Peter Kokot (maastermedia) + - Christian Flothmann (xabbuh) - Jérémie Augustin (jaugustin) - David Buchmann (dbu) - Jérôme Tamarelle (gromnan) - Tigran Azatyan (tigranazatyan) - Javier Eguiluz (javier.eguiluz) - - Christian Flothmann (xabbuh) - Rafael Dohms (rdohms) - Richard Shank (iampersistent) - Gordon Franke (gimler) @@ -146,6 +146,7 @@ Symfony2 is the result of the work of many people who made the code better - Manuel Reinhard (sprain) - Danny Berger (dpb587) - Xavier Montaña Carreras (xmontana) + - Michele Orselli (orso) - Xavier Perez - Arjen Brouwer (arjenjb) - Katsuhiro OGAWA @@ -168,7 +169,6 @@ Symfony2 is the result of the work of many people who made the code better - Robert Kiss (kepten) - Kim Hemsø Rasmussen (kimhemsoe) - Wouter Van Hecke - - Michele Orselli (orso) - Michael Holm (hollo) - Marc Weistroff (futurecat) - Pierre-Yves LEBECQ (pylebecq) @@ -231,6 +231,7 @@ Symfony2 is the result of the work of many people who made the code better - Ismael Ambrosi (iambrosi) - Shein Alexey - Issei Murasawa (issei_m) + - hacfi (hifi) - Joe Lencioni - Kai - Xavier HAUSHERR @@ -274,6 +275,7 @@ Symfony2 is the result of the work of many people who made the code better - François-Xavier de Guillebon (de-gui_f) - boombatower - Fabrice Bernhard (fabriceb) + - Clément Gautier (clementgautier) - Fabian Lange (codingfabian) - Yoshio HANAWA - Baptiste Clavié (talus) @@ -294,11 +296,11 @@ Symfony2 is the result of the work of many people who made the code better - Maks Slesarenko - Markus Lanthaler (lanthaler) - Vicent Soria Durá (vicentgodella) + - Chris Wilkinson (thewilkybarkid) - Ioan Negulescu - Andrew Udvare (audvare) - alexpods - Erik Trapman (eriktrapman) - - hacfi (hifi) - De Cock Xavier (xdecock) - Alex Pott - Norbert Orzechowicz (norzechowicz) @@ -365,6 +367,7 @@ Symfony2 is the result of the work of many people who made the code better - Arturs Vonda - Sascha Grossenbacher - Ben Davies (bendavies) + - Simon Schick (simonsimcity) - Hossein Bukhamsin - Paweł Wacławczyk (pwc) - Oleg Zinchenko (cystbear) @@ -372,6 +375,7 @@ Symfony2 is the result of the work of many people who made the code better - Johannes Klauss (cloppy) - Evan Villemez - fzerorubigd + - Benjamin Grandfond (benjamin) - Tiago Brito (blackmx) - Richard van den Brand (ricbra) - develop @@ -403,7 +407,6 @@ Symfony2 is the result of the work of many people who made the code better - Marek Štípek (maryo) - John Bohn (jbohn) - Jakub Škvára (jskvara) - - Chris Wilkinson (thewilkybarkid) - Andrew Hilobok (hilobok) - Christian Soronellas (theunic) - Jérôme Vieilledent (lolautruche) @@ -532,7 +535,6 @@ Symfony2 is the result of the work of many people who made the code better - jfcixmedia - Martijn Evers - Benjamin Paap (benjaminpaap) - - Simon Schick (simonsimcity) - Christian - Sergii Smertin (nfx) - Eddie Jaoude @@ -550,6 +552,7 @@ Symfony2 is the result of the work of many people who made the code better - Benoit Garret - DerManoMann - Asmir Mustafic (goetas) + - Julien Bianchi (jubianchi) - Marcin Chwedziak - Roland Franssen (ro0) - Maciej Malarz @@ -562,20 +565,22 @@ Symfony2 is the result of the work of many people who made the code better - kaiwa - Albert Ganiev (helios-ag) - Neil Katin + - David Otton - peter - Artem Kolesnikov (tyomo4ka) - Gustavo Adrian - - Clément Gautier (clementgautier) - Yannick - Luc Vieillescazes (iamluc) - Eduardo García Sanz (coma) - David de Boer (ddeboer) - Brooks Boyd - Roger Webb + - Dmitriy Simushev - Max Voloshin (maxvoloshin) - Nicolas Fabre (nfabre) - Raul Rodriguez (raul782) - Patrick Landolt (scube) + - WybrenKoelmans - Derek Lambert - Felicitus - Krzysztof Przybyszewski @@ -730,6 +735,7 @@ Symfony2 is the result of the work of many people who made the code better - catch - Alexandre Segura - Josef Cech + - Nate (frickenate) - Matthew Foster (mfoster) - Maximilian Reichel (phramz) - Paul Seiffert (seiffert) @@ -750,7 +756,6 @@ Symfony2 is the result of the work of many people who made the code better - Andrey Ryaguzov - Gunther Konig - František Bereň - - Benjamin Grandfond (benjamin) - Christoph Nissle (derstoffel) - Ionel Scutelnicu (ionelscutelnicu) - Johnny Peck (johnnypeck) @@ -875,6 +880,7 @@ Symfony2 is the result of the work of many people who made the code better - Yorkie Chadwick (yorkie76) - Yanick Witschi - Ondrej Mirtes + - akimsko - Youpie - srsbiz - Nicolas A. Bérard-Nault @@ -1010,6 +1016,7 @@ Symfony2 is the result of the work of many people who made the code better - Adam Monsen (meonkeys) - ollie harridge (ollietb) - Paweł Szczepanek (pauluz) + - Christian López Espínola (penyaskito) - Petr Jaroš (petajaros) - Philipp Hoffmann (philipphoffmann) - Alex Carol (picard89) From 75e07e6bde6391a6f49c8546a43740c80ac1b06b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 15 Jul 2014 16:20:44 +0200 Subject: [PATCH 09/25] updated VERSION for 2.3.18 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 3718155ade..1143fe52dd 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -59,12 +59,12 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.3.18-DEV'; + const VERSION = '2.3.18'; const VERSION_ID = '20318'; const MAJOR_VERSION = '2'; const MINOR_VERSION = '3'; const RELEASE_VERSION = '18'; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = ''; /** * Constructor. From ace5a29867c218cb0bc4dc379a7aa549e7e1aea5 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 15 Jul 2014 21:58:41 +0200 Subject: [PATCH 10/25] bumped Symfony version to 2.3.19 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 1143fe52dd..e14f57967b 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -59,12 +59,12 @@ abstract class Kernel implements KernelInterface, TerminableInterface protected $startTime; protected $loadClassCache; - const VERSION = '2.3.18'; - const VERSION_ID = '20318'; + const VERSION = '2.3.19-DEV'; + const VERSION_ID = '20319'; const MAJOR_VERSION = '2'; const MINOR_VERSION = '3'; - const RELEASE_VERSION = '18'; - const EXTRA_VERSION = ''; + const RELEASE_VERSION = '19'; + const EXTRA_VERSION = 'DEV'; /** * Constructor. From ff0bb01a9180f39876c4d73c5ff7fc043e84eccf Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Sat, 14 Jun 2014 14:38:29 +0200 Subject: [PATCH 11/25] [Process] Reduce I/O load on Windows platform --- src/Symfony/Component/Process/ProcessPipes.php | 2 ++ .../Component/Process/Tests/AbstractProcessTest.php | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Process/ProcessPipes.php b/src/Symfony/Component/Process/ProcessPipes.php index f35d1c1287..8b11a930fc 100644 --- a/src/Symfony/Component/Process/ProcessPipes.php +++ b/src/Symfony/Component/Process/ProcessPipes.php @@ -284,6 +284,8 @@ class ProcessPipes private function readStreams($blocking, $close = false) { if (empty($this->pipes)) { + usleep(Process::TIMEOUT_PRECISION * 1E4); + return array(); } diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index 91b8aca20d..d5bb967972 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -583,7 +583,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase } $duration = microtime(true) - $start; - $this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + // Windows is a bit slower as it read file handles, then allow twice the precision + $maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION; + } else { + $maxDuration = $timeout + Process::TIMEOUT_PRECISION; + } + + $this->assertLessThan($maxDuration, $duration); } public function testCheckTimeoutOnNonStartedProcess() From d4189350c07ffaf1c02d37cf2c0a1a50b1a208a5 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Wed, 16 Jul 2014 11:55:07 +0200 Subject: [PATCH 12/25] [Process] Fix unit tests on Windows platform --- .../Process/Tests/ExecutableFinderTest.php | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php index abaa47d8dd..e728c0ee73 100644 --- a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php @@ -47,9 +47,9 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase $this->setPath(dirname(PHP_BINARY)); $finder = new ExecutableFinder; - $result = $finder->find(basename(PHP_BINARY)); + $result = $finder->find($this->getPhpBinaryName()); - $this->assertEquals($result, PHP_BINARY); + $this->assertSamePath(PHP_BINARY, $result); } public function testFindWithDefault() @@ -83,9 +83,9 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase $extraDirs = array(dirname(PHP_BINARY)); $finder = new ExecutableFinder; - $result = $finder->find(basename(PHP_BINARY), null, $extraDirs); + $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs); - $this->assertEquals(PHP_BINARY, $result); + $this->assertSamePath(PHP_BINARY, $result); } public function testFindWithOpenBaseDir() @@ -105,8 +105,22 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase ini_set('open_basedir', dirname(PHP_BINARY).PATH_SEPARATOR.'/'); $finder = new ExecutableFinder; - $result = $finder->find(basename(PHP_BINARY)); + $result = $finder->find($this->getPhpBinaryName()); - $this->assertEquals(PHP_BINARY, $result); + $this->assertSamePath(PHP_BINARY, $result); + } + + private function assertSamePath($expected, $tested) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals(strtolower($expected), strtolower($tested)); + } else { + $this->assertEquals($expected, $tested); + } + } + + private function getPhpBinaryName() + { + return basename(PHP_BINARY, defined('PHP_WINDOWS_VERSION_BUILD') ? '.exe' : ''); } } From cec0a45ff5951403965d79e085e43538988f2625 Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Sat, 12 Jul 2014 16:27:05 +0200 Subject: [PATCH 13/25] [Process] Adjust PR #11264, make it Windows compatible and fix CS --- .../Component/Process/Tests/AbstractProcessTest.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index 3074874743..fb34351169 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -287,8 +287,15 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase } } - public function testZeroAsOutput(){ - $p = $this->getProcess('printf 0'); + public function testZeroAsOutput() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line + $p = $this->getProcess('echo | set /p dummyName=0'); + } else { + $p = $this->getProcess('printf 0'); + } + $p->run(); $this->assertSame('0', $p->getOutput()); } From 3176f8bb98c9634be77d4b5b50df054a47f2079c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Muszy=C5=84ski?= Date: Wed, 16 Jul 2014 21:26:05 +0200 Subject: [PATCH 14/25] [Translator][FrameworkBundle] Added @ to the list of allowed chars in Translator --- .../Tests/Translation/TranslatorTest.php | 17 ++++++++++++++--- .../Translation/Tests/TranslatorTest.php | 3 +-- .../Component/Translation/Translator.php | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index 715c44fade..9f2012816a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -45,7 +45,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase { $translator = $this->getTranslator($this->getLoader()); $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); + $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin')); $this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('bar (EN)', $translator->trans('bar')); @@ -55,6 +55,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); + $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); } public function testTransWithCaching() @@ -62,7 +63,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase // prime the cache $translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir)); $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); + $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin')); $this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('bar (EN)', $translator->trans('bar')); @@ -72,12 +73,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); + $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); // do it another time as the cache is primed now $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir)); $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); + $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin')); $this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('bar (EN)', $translator->trans('bar')); @@ -87,6 +89,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); + $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); } public function testGetLocale() @@ -185,6 +188,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase 'foobarbaz' => 'foobarbaz (fr.UTF-8)', )))) ; + $loader + ->expects($this->at(6)) + ->method('load') + ->will($this->returnValue($this->getCatalogue('sr@latin', array( + 'foobarbax' => 'foobarbax (sr@latin)', + )))) + ; return $loader; } @@ -216,6 +226,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese $translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese $translator->addResource('loader', 'foo', 'fr.UTF-8'); + $translator->addResource('loader', 'foo', 'sr@latin'); // Latin Serbian return $translator; } diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index 40a0b1d0d9..cf9c8df659 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -103,7 +103,6 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals('bar (fr)', $translator->trans('bar')); } - /** * @dataProvider getInvalidLocalesTests * @expectedException \InvalidArgumentException @@ -329,7 +328,6 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase // no assertion. this method just asserts that no exception is thrown } - public function getTransFileTests() { return array( @@ -429,6 +427,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase array('fr_FR'), array('fr.FR'), array('fr-FR.UTF8'), + array('sr@latin'), ); } diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index e54b300ffd..8e3cda857c 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -308,7 +308,7 @@ class Translator implements TranslatorInterface */ private function assertValidLocale($locale) { - if (0 !== preg_match('/[^a-z0-9_\\.\\-]+/i', $locale, $match)) { + if (0 !== preg_match('/[^a-z0-9@_\\.\\-]+/i', $locale, $match)) { throw new \InvalidArgumentException(sprintf('Invalid locale: %s.', $locale)); } } From 291cbf9efa14460a2c77a6d6ca9bbcc2603db639 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 17 Jul 2014 16:48:59 +0200 Subject: [PATCH 15/25] [Validator] Backported #11410 to 2.3: Object initializers are called only once per object --- .../Validator/Tests/Fixtures/Entity.php | 1 + .../Validator/Tests/ValidationVisitorTest.php | 48 +++++++++++++++++++ .../Component/Validator/ValidationVisitor.php | 17 ++++--- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php index e1cb3e0490..f9e9e7ff3b 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php @@ -35,6 +35,7 @@ class Entity extends EntityParent implements EntityInterface public $reference; private $internal; public $data = 'Overridden data'; + public $initialized = false; public function __construct($internal = null) { diff --git a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php b/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php index 2868f57a82..f2838737a2 100644 --- a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php +++ b/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Validator\Tests; +use Symfony\Component\Validator\Constraints\Callback; +use Symfony\Component\Validator\ExecutionContextInterface; use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Tests\Fixtures\Reference; @@ -561,4 +563,50 @@ class ValidationVisitorTest extends \PHPUnit_Framework_TestCase $this->visitor->validate($entity, 'Default', ''); } + + public function testInitializeObjectsOnFirstValidation() + { + $test = $this; + $entity = new Entity(); + $entity->initialized = false; + + // prepare initializers that set "initialized" to true + $initializer1 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); + $initializer2 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); + + $initializer1->expects($this->once()) + ->method('initialize') + ->with($entity) + ->will($this->returnCallback(function ($object) { + $object->initialized = true; + })); + + $initializer2->expects($this->once()) + ->method('initialize') + ->with($entity); + + $this->visitor = new ValidationVisitor('Root', $this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator(), null, array( + $initializer1, + $initializer2 + )); + + // prepare constraint which + // * checks that "initialized" is set to true + // * validates the object again + $callback = function ($object, ExecutionContextInterface $context) use ($test) { + $test->assertTrue($object->initialized); + + // validate again in same group + $context->validate($object); + + // validate again in other group + $context->validate($object, '', 'SomeGroup'); + }; + + $this->metadata->addConstraint(new Callback(array($callback))); + + $this->visitor->validate($entity, 'Default', ''); + + $this->assertTrue($entity->initialized); + } } diff --git a/src/Symfony/Component/Validator/ValidationVisitor.php b/src/Symfony/Component/Validator/ValidationVisitor.php index ddff8adc60..510c61e2eb 100644 --- a/src/Symfony/Component/Validator/ValidationVisitor.php +++ b/src/Symfony/Component/Validator/ValidationVisitor.php @@ -127,16 +127,19 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo return; } + // Initialize if the object wasn't initialized before + if (!isset($this->validatedObjects[$hash])) { + foreach ($this->objectInitializers as $initializer) { + if (!$initializer instanceof ObjectInitializerInterface) { + throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.'); + } + $initializer->initialize($value); + } + } + // Remember validating this object before starting and possibly // traversing the object graph $this->validatedObjects[$hash][$group] = true; - - foreach ($this->objectInitializers as $initializer) { - if (!$initializer instanceof ObjectInitializerInterface) { - throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.'); - } - $initializer->initialize($value); - } } // Validate arrays recursively by default, otherwise every driver needs From 678766900be69d7533ee1e74d0d86cf355d57756 Mon Sep 17 00:00:00 2001 From: Jakub Zalas Date: Sat, 19 Jul 2014 21:31:34 +0100 Subject: [PATCH 16/25] [DependencyInjection] Pass a Scope instance instead of a scope name. --- .../DependencyInjection/Dumper/GraphvizDumper.php | 5 +++-- .../Tests/Dumper/GraphvizDumperTest.php | 7 +++++++ .../Tests/Fixtures/containers/container18.php | 14 ++++++++++++++ .../Tests/Fixtures/graphviz/services18.dot | 8 ++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container18.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services18.dot diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php index 5ee1b7eea0..1ffe142af4 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Scope; /** * GraphvizDumper dumps a service container as a graphviz file. @@ -193,8 +194,8 @@ class GraphvizDumper extends Dumper $container->setDefinitions($this->container->getDefinitions()); $container->setAliases($this->container->getAliases()); $container->setResources($this->container->getResources()); - foreach ($this->container->getScopes() as $scope) { - $container->addScope($scope); + foreach ($this->container->getScopes() as $scope => $parentScope) { + $container->addScope(new Scope($scope, $parentScope)); } foreach ($this->container->getExtensions() as $extension) { $container->registerExtension($extension); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php index 0dc1ce8de1..3db11504ad 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/GraphvizDumperTest.php @@ -62,4 +62,11 @@ class GraphvizDumperTest extends \PHPUnit_Framework_TestCase $dumper = new GraphvizDumper($container); $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services14.dot')), $dumper->dump(), '->dump() dumps services'); } + + public function testDumpWithScopes() + { + $container = include self::$fixturesPath.'/containers/container18.php'; + $dumper = new GraphvizDumper($container); + $this->assertEquals(str_replace('%path%', __DIR__, file_get_contents(self::$fixturesPath.'/graphviz/services18.dot')), $dumper->dump(), '->dump() dumps services'); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container18.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container18.php new file mode 100644 index 0000000000..0248ed4627 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container18.php @@ -0,0 +1,14 @@ +addScope(new Scope('request')); +$container-> + register('foo', 'FooClass')-> + setScope('request') +; +$container->compile(); + +return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services18.dot b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services18.dot new file mode 100644 index 0000000000..4fbcceef36 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services18.dot @@ -0,0 +1,8 @@ +digraph sc { + ratio="compress" + node [fontsize="11" fontname="Arial" shape="record"]; + edge [fontsize="9" fontname="Arial" color="grey" arrowhead="open" arrowsize="0.5"]; + + node_foo [label="foo\nFooClass\n", shape=record, fillcolor="#eeeeee", style="filled"]; + node_service_container [label="service_container\nSymfony\\Component\\DependencyInjection\\ContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"]; +} From 02eb765a9c45fef7a925401381500d680d35d9af Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 19 Jul 2014 17:29:08 -0600 Subject: [PATCH 17/25] [Process] Fixes issue #11421 --- src/Symfony/Component/Process/ExecutableFinder.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Symfony/Component/Process/ExecutableFinder.php b/src/Symfony/Component/Process/ExecutableFinder.php index 44f1605fa9..6ae74dca17 100644 --- a/src/Symfony/Component/Process/ExecutableFinder.php +++ b/src/Symfony/Component/Process/ExecutableFinder.php @@ -59,8 +59,7 @@ class ExecutableFinder if (is_dir($path)) { $dirs[] = $path; } else { - $file = str_replace(dirname($path), '', $path); - if ($file == $name && is_executable($path)) { + if (basename($path) == $name && is_executable($path)) { return $path; } } From 9f4313cf6f550d829e761e1add5ee8f74f5861a6 Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 19 Jul 2014 17:48:53 -0600 Subject: [PATCH 18/25] [Process] Add test to verify fix for issue #11421 --- .../Process/Tests/ExecutableFinderTest.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php index e728c0ee73..0a905e946c 100644 --- a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php @@ -110,6 +110,27 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase $this->assertSamePath(PHP_BINARY, $result); } + public function testFindProcessInOpenBasedir() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + if (!defined('PHP_BINARY')) { + $this->markTestSkipped('Requires the PHP_BINARY constant'); + } + + $execPath = __DIR__.DIRECTORY_SEPARATOR.'SignalListener.php'; + + $this->setPath(''); + ini_set('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/'); + + $finder = new ExecutableFinder; + $result = $finder->find($this->getPhpBinaryName(), false); + + $this->assertSamePath(PHP_BINARY, $result); + } + private function assertSamePath($expected, $tested) { if (defined('PHP_WINDOWS_VERSION_BUILD')) { From 4cf50e8d3036664d51efb7709e770d209358cd2b Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 20 Jul 2014 22:50:55 -0600 Subject: [PATCH 19/25] Bring code into standard --- src/Symfony/Component/Process/Tests/ExecutableFinderTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php index 0a905e946c..3b5bff26e9 100644 --- a/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php +++ b/src/Symfony/Component/Process/Tests/ExecutableFinderTest.php @@ -120,12 +120,12 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase $this->markTestSkipped('Requires the PHP_BINARY constant'); } - $execPath = __DIR__.DIRECTORY_SEPARATOR.'SignalListener.php'; + $execPath = __DIR__.'/SignalListener.php'; $this->setPath(''); ini_set('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/'); - $finder = new ExecutableFinder; + $finder = new ExecutableFinder(); $result = $finder->find($this->getPhpBinaryName(), false); $this->assertSamePath(PHP_BINARY, $result); From 537c39b11ec808ca5c29a50e3367472449a87aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 22 Jul 2014 23:54:07 +0200 Subject: [PATCH 20/25] Optimize assertLocale regexp --- src/Symfony/Component/Translation/Translator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 8e3cda857c..d13adc07c6 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -308,7 +308,7 @@ class Translator implements TranslatorInterface */ private function assertValidLocale($locale) { - if (0 !== preg_match('/[^a-z0-9@_\\.\\-]+/i', $locale, $match)) { + if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { throw new \InvalidArgumentException(sprintf('Invalid locale: %s.', $locale)); } } From c9742efe991732b22ec9fedf83ae2a926af99b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 24 Jul 2014 17:02:45 +0200 Subject: [PATCH 21/25] [Translator] Use quote to surround invalid locale --- src/Symfony/Component/Translation/Translator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index d13adc07c6..0ed30526d2 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -309,7 +309,7 @@ class Translator implements TranslatorInterface private function assertValidLocale($locale) { if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) { - throw new \InvalidArgumentException(sprintf('Invalid locale: %s.', $locale)); + throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale)); } } } From f401ab9032378884e9e5bb9d6727b15c3e55e690 Mon Sep 17 00:00:00 2001 From: Benjamin Cremer Date: Wed, 9 Jul 2014 09:53:11 +0200 Subject: [PATCH 22/25] Fixed server HTTP_HOST port uri conversion --- src/Symfony/Component/BrowserKit/Client.php | 6 +++++- .../Component/BrowserKit/Tests/ClientTest.php | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index a69e1625c0..befc0371f4 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -304,7 +304,11 @@ abstract class Client $uri = $this->getAbsoluteUri($uri); if (isset($server['HTTP_HOST'])) { - $uri = preg_replace('{^(https?\://)'.parse_url($uri, PHP_URL_HOST).'}', '${1}'.$server['HTTP_HOST'], $uri); + if ($port = parse_url($uri, PHP_URL_PORT)) { + $port = ':'.$port; + } + + $uri = preg_replace('{^(https?\://)'.parse_url($uri, PHP_URL_HOST).$port.'}', '${1}'.$server['HTTP_HOST'], $uri); } if (isset($server['HTTPS'])) { diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index 2612bb26c4..a5eade2a58 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -210,6 +210,24 @@ class ClientTest extends \PHPUnit_Framework_TestCase $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); } + public function testRequestURIConversionByServerHost() + { + $client = new TestClient(); + + $server = array('HTTP_HOST' => 'www.example.com:8000'); + $parameters = array(); + $files = array(); + + $client->request('GET', 'http://example.com', $parameters, $files, $server); + $this->assertEquals('http://www.example.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to add port'); + + $client->request('GET', 'http://example.com:8888', $parameters, $files, $server); + $this->assertEquals('http://www.example.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to modify existing port'); + + $client->request('GET', 'http://example.com:8000', $parameters, $files, $server); + $this->assertEquals('http://www.example.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST respects correct set port'); + } + public function testRequestReferer() { $client = new TestClient(); From 103fd88b403f0ff5ddec5284344ffb64fc0183df Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 25 Jul 2014 07:47:26 +0200 Subject: [PATCH 23/25] [BrowserKit] refactor code and fix unquoted regex --- src/Symfony/Component/BrowserKit/Client.php | 24 ++++++++++--------- .../Component/BrowserKit/Tests/ClientTest.php | 14 +++++------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/Client.php b/src/Symfony/Component/BrowserKit/Client.php index befc0371f4..6e91305a74 100644 --- a/src/Symfony/Component/BrowserKit/Client.php +++ b/src/Symfony/Component/BrowserKit/Client.php @@ -304,11 +304,7 @@ abstract class Client $uri = $this->getAbsoluteUri($uri); if (isset($server['HTTP_HOST'])) { - if ($port = parse_url($uri, PHP_URL_PORT)) { - $port = ':'.$port; - } - - $uri = preg_replace('{^(https?\://)'.parse_url($uri, PHP_URL_HOST).$port.'}', '${1}'.$server['HTTP_HOST'], $uri); + $uri = preg_replace('{^(https?\://)'.preg_quote($this->extractHost($uri)).'}', '${1}'.$server['HTTP_HOST'], $uri); } if (isset($server['HTTPS'])) { @@ -321,12 +317,7 @@ abstract class Client $server['HTTP_REFERER'] = $this->history->current()->getUri(); } - $server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST); - - if ($port = parse_url($uri, PHP_URL_PORT)) { - $server['HTTP_HOST'] .= ':'.$port; - } - + $server['HTTP_HOST'] = $this->extractHost($uri); $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME); $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content); @@ -623,4 +614,15 @@ abstract class Client return $server; } + + private function extractHost($uri) + { + $host = parse_url($uri, PHP_URL_HOST); + + if ($port = parse_url($uri, PHP_URL_PORT)) { + return $host.':'.$port; + } + + return $host; + } } diff --git a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php index a5eade2a58..cd37fcf76a 100644 --- a/src/Symfony/Component/BrowserKit/Tests/ClientTest.php +++ b/src/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -214,18 +214,18 @@ class ClientTest extends \PHPUnit_Framework_TestCase { $client = new TestClient(); - $server = array('HTTP_HOST' => 'www.example.com:8000'); + $server = array('HTTP_HOST' => 'www.exampl+e.com:8000'); $parameters = array(); $files = array(); - $client->request('GET', 'http://example.com', $parameters, $files, $server); - $this->assertEquals('http://www.example.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to add port'); + $client->request('GET', 'http://exampl+e.com', $parameters, $files, $server); + $this->assertEquals('http://www.exampl+e.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to add port'); - $client->request('GET', 'http://example.com:8888', $parameters, $files, $server); - $this->assertEquals('http://www.example.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to modify existing port'); + $client->request('GET', 'http://exampl+e.com:8888', $parameters, $files, $server); + $this->assertEquals('http://www.exampl+e.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST to modify existing port'); - $client->request('GET', 'http://example.com:8000', $parameters, $files, $server); - $this->assertEquals('http://www.example.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST respects correct set port'); + $client->request('GET', 'http://exampl+e.com:8000', $parameters, $files, $server); + $this->assertEquals('http://www.exampl+e.com:8000', $client->getRequest()->getUri(), '->request() uses HTTP_HOST respects correct set port'); } public function testRequestReferer() From eb68662360d584f2063788c6dcfaee52b6cc7803 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 22 Jul 2014 18:04:22 +0200 Subject: [PATCH 24/25] [Process] fix signal handling in wait() wait() throws an exception when the process was terminated by a signal. This should not happen when the termination was requested by calling either the stop() or the signal() method (for example, inside a callback which is passed to wait()). --- src/Symfony/Component/Process/Process.php | 10 ++++- .../Process/Tests/SimpleProcessTest.php | 44 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 2ccad53d77..3185633fbf 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -64,6 +64,8 @@ class Process /** @var ProcessPipes */ private $processPipes; + private $latestSignal; + private static $sigchild; /** @@ -321,7 +323,7 @@ class Process usleep(1000); } - if ($this->processInformation['signaled']) { + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); } @@ -661,7 +663,8 @@ class Process throw new RuntimeException('Unable to kill the process'); } } - proc_terminate($this->process); + // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); do { usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); @@ -1158,6 +1161,7 @@ class Process $this->stdout = null; $this->stderr = null; $this->process = null; + $this->latestSignal = null; $this->status = self::STATUS_READY; $this->incrementalOutputOffset = 0; $this->incrementalErrorOutputOffset = 0; @@ -1201,6 +1205,8 @@ class Process return false; } + $this->latestSignal = $signal; + return true; } diff --git a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php index 69ad3d5b09..cb06f28968 100644 --- a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php @@ -147,6 +147,50 @@ class SimpleProcessTest extends AbstractProcessTest parent::testSignalWithWrongNonIntSignal(); } + public function testStopTerminatesProcessCleanly() + { + try { + $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + $process->stop(); + }); + } catch (RuntimeException $e) { + $this->fail('A call to stop() is not expected to cause wait() to throw a RuntimeException'); + } + } + + public function testKillSignalTerminatesProcessCleanly() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + + try { + $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(SIGKILL); + } + }); + } catch (RuntimeException $e) { + $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + } + + public function testTermSignalTerminatesProcessCleanly() + { + $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + + try { + $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(SIGTERM); + } + }); + } catch (RuntimeException $e) { + $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + } + /** * {@inheritdoc} */ From 5939d34c17350684a0c5004591df7021d2f5ec9b Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Fri, 25 Jul 2014 09:46:51 +0200 Subject: [PATCH 25/25] [Process] Fix unit tests in sigchild environment --- .../Process/Tests/SigchildDisabledProcessTest.php | 5 +++++ .../Process/Tests/SigchildEnabledProcessTest.php | 15 +++++++++++++++ .../Component/Process/Tests/SimpleProcessTest.php | 7 +++++++ 3 files changed, 27 insertions(+) diff --git a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php index 798e66a571..383a532203 100644 --- a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php @@ -211,6 +211,11 @@ class SigchildDisabledProcessTest extends AbstractProcessTest $this->markTestSkipped('Signal is not supported in sigchild environment'); } + public function testRunProcessWithTimeout() + { + $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php index 65dd4bb573..37a064188f 100644 --- a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php @@ -120,6 +120,21 @@ class SigchildEnabledProcessTest extends AbstractProcessTest parent::testStartAfterATimeout(); } + public function testStopWithTimeoutIsActuallyWorking() + { + $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); + } + + public function testRunProcessWithTimeout() + { + $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); + } + + public function testCheckTimeoutOnStartedProcess() + { + $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php index cb06f28968..ccaa78df24 100644 --- a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php @@ -191,6 +191,13 @@ class SimpleProcessTest extends AbstractProcessTest } } + public function testStopWithTimeoutIsActuallyWorking() + { + $this->skipIfPHPSigchild(); + + parent::testStopWithTimeoutIsActuallyWorking(); + } + /** * {@inheritdoc} */