Merge branch '5.1' into 5.2

* 5.1:
  Display debug info
  [HttpClient] don't fallback to HTTP/1.1 when HTTP/2 streams break
  fix lexing nested sequences/mappings
This commit is contained in:
Christian Flothmann 2020-11-21 10:39:55 +01:00
commit bdadbf31a0
6 changed files with 320 additions and 84 deletions

View File

@ -99,17 +99,12 @@ jobs:
- name: Install system dependencies
run: |
echo "::group::add apt sources"
sudo wget -O - http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add -
echo "deb http://packages.couchbase.com/ubuntu bionic bionic/main" | sudo tee /etc/apt/sources.list.d/couchbase.list
echo "::endgroup::"
echo "::group::apt-get update"
sudo apt-get update
echo "::endgroup::"
echo "::group::install tools & libraries"
sudo apt-get install libcouchbase-dev librdkafka-dev
sudo apt-get install librdkafka-dev
echo "::endgroup::"
- name: Configure Couchbase
@ -128,6 +123,11 @@ jobs:
php-version: "${{ matrix.php }}"
tools: pecl
- name: Display versions
run: |
php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;'
php -i
- name: Load fixtures
uses: docker://bitnami/openldap
with:

View File

@ -42,7 +42,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter
public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
if (!static::isSupported()) {
throw new CacheException('Couchbase >= 2.6.0 is required.');
throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
}
$this->maxIdLength = static::MAX_KEY_LENGTH;
@ -66,7 +66,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter
}
if (!static::isSupported()) {
throw new CacheException('Couchbase >= 2.6.0 is required.');
throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.');
}
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
@ -125,7 +125,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter
public static function isSupported(): bool
{
return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=');
return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<');
}
private static function getOptions(string $options): array

View File

@ -16,7 +16,8 @@ use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\CouchbaseBucketAdapter;
/**
* @requires extension couchbase 2.6.0
* @requires extension couchbase <3.0.0
* @requires extension couchbase >=2.6.0
* @group integration
*
* @author Antonio Jose Cerezo Aranda <aj.cerezo@gmail.com>
@ -32,6 +33,10 @@ class CouchbaseBucketAdapterTest extends AdapterTestCase
public static function setupBeforeClass(): void
{
if (!CouchbaseBucketAdapter::isSupported()) {
self::markTestSkipped('Couchbase >= 2.6.0 < 3.0.0 is required.');
}
self::$client = AbstractAdapter::createConnection('couchbase://'.getenv('COUCHBASE_HOST').'/cache',
['username' => getenv('COUCHBASE_USER'), 'password' => getenv('COUCHBASE_PASS')]
);

View File

@ -306,10 +306,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
curl_multi_remove_handle($multi->handle, $ch);
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
if ('1' === $waitFor[1]) {
curl_setopt($ch, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1);
}
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
if (0 === curl_multi_add_handle($multi->handle, $ch)) {
continue;

View File

@ -358,7 +358,7 @@ class Parser
}
try {
return Inline::parse($this->parseQuotedString($this->currentLine), $flags, $this->refs);
return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
@ -371,7 +371,7 @@ class Parser
}
try {
$parsedMapping = Inline::parse($this->lexInlineMapping($this->currentLine), $flags, $this->refs);
$parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs);
while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) {
@ -392,7 +392,7 @@ class Parser
}
try {
$parsedSequence = Inline::parse($this->lexInlineSequence($this->currentLine), $flags, $this->refs);
$parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs);
while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) {
@ -667,6 +667,11 @@ class Parser
return implode("\n", $data);
}
private function hasMoreLines(): bool
{
return (\count($this->lines) - 1) > $this->currentLineNb;
}
/**
* Moves the parser to the next line.
*/
@ -744,9 +749,13 @@ class Parser
try {
if ('' !== $value && '{' === $value[0]) {
return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs);
$cursor = \strlen($this->currentLine) - \strlen($value);
return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs);
} elseif ('' !== $value && '[' === $value[0]) {
return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs);
$cursor = \strlen($this->currentLine) - \strlen($value);
return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs);
}
$quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
@ -1145,107 +1154,148 @@ class Parser
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
private function parseQuotedString(string $yaml): ?string
private function lexInlineQuotedString(int &$cursor = 0): string
{
if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) {
throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml));
}
$quotation = $this->currentLine[$cursor];
$value = $quotation;
++$cursor;
$lines = [$yaml];
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
while ($this->moveToNextLine()) {
$lines[] = $this->currentLine;
if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) {
break;
}
}
$value = '';
for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) {
$trimmedLine = trim($lines[$i]);
if ('' === $trimmedLine) {
do {
if ($this->isCurrentLineBlank()) {
$value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' ';
}
if ('' !== $trimmedLine && '\\' === $lines[$i][-1]) {
$value .= ltrim(substr($lines[$i], 0, -1));
} elseif ('' !== $trimmedLine) {
$value .= $trimmedLine;
for (; \strlen($this->currentLine) > $cursor; ++$cursor) {
switch ($this->currentLine[$cursor]) {
case '\\':
if (isset($this->currentLine[++$cursor])) {
$value .= '\\'.$this->currentLine[$cursor];
}
break;
case $quotation:
++$cursor;
if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) {
$value .= "''";
break;
}
return $value.$quotation;
default:
$value .= $this->currentLine[$cursor];
}
}
if ('' === $trimmedLine) {
if ($this->isCurrentLineBlank()) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === $lines[$i][-1]) {
} elseif ('\\' === $this->currentLine[-1]) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
}
}
return $value;
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
throw new ParseException('Malformed inline YAML string');
}
private function lexInlineMapping(string $yaml): string
private function lexUnquotedString(int &$cursor): string
{
if ('' === $yaml || '{' !== $yaml[0]) {
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
}
$offset = $cursor;
$cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) {
}
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
return $yaml;
}
$lines = [$yaml];
while ($this->moveToNextLine()) {
$lines[] = $this->currentLine;
}
return implode("\n", $lines);
return substr($this->currentLine, $offset, $cursor - $offset);
}
private function lexInlineSequence(string $yaml): string
private function lexInlineMapping(int &$cursor = 0): string
{
if ('' === $yaml || '[' !== $yaml[0]) {
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
}
return $this->lexInlineStructure($cursor, '}');
}
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) {
}
private function lexInlineSequence(int &$cursor = 0): string
{
return $this->lexInlineStructure($cursor, ']');
}
if (isset($yaml[$i]) && ']' === $yaml[$i]) {
return $yaml;
}
private function lexInlineStructure(int &$cursor, string $closingTag): string
{
$value = $this->currentLine[$cursor];
++$cursor;
$value = $yaml;
do {
$this->consumeWhitespaces($cursor);
while ($this->moveToNextLine()) {
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) {
while (isset($this->currentLine[$cursor])) {
switch ($this->currentLine[$cursor]) {
case '"':
case "'":
$value .= $this->lexInlineQuotedString($cursor);
break;
case ':':
case ',':
$value .= $this->currentLine[$cursor];
++$cursor;
break;
case '{':
$value .= $this->lexInlineMapping($cursor);
break;
case '[':
$value .= $this->lexInlineSequence($cursor);
break;
case $closingTag:
$value .= $this->currentLine[$cursor];
++$cursor;
return $value;
case '#':
break 2;
default:
$value .= $this->lexUnquotedString($cursor);
}
if ($this->consumeWhitespaces($cursor)) {
$value .= ' ';
}
}
$trimmedValue = trim($this->currentLine);
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
if ('' !== $trimmedValue && '#' === $trimmedValue[0]) {
continue;
throw new ParseException('Malformed inline YAML string');
}
private function consumeWhitespaces(int &$cursor): bool
{
$whitespacesConsumed = 0;
do {
$whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor);
$whitespacesConsumed += $whitespaceOnlyTokenLength;
$cursor += $whitespaceOnlyTokenLength;
if (isset($this->currentLine[$cursor])) {
return 0 < $whitespacesConsumed;
}
$value .= $trimmedValue;
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
break;
if ($this->hasMoreLines()) {
$cursor = 0;
}
}
} while ($this->moveToNextLine());
return $value;
return 0 < $whitespacesConsumed;
}
}

View File

@ -1659,6 +1659,16 @@ EOF;
'foo': 'bar',
'bar': 'baz'
}
YAML
,
],
'mapping with unquoted strings and values' => [
['foo' => 'bar', 'bar' => 'baz'],
<<<YAML
{
foo: bar,
bar: baz
}
YAML
,
],
@ -1672,6 +1682,53 @@ YAML
YAML
,
],
'sequence with unquoted items' => [
['foo', 'bar'],
<<<YAML
[
foo,
bar
]
YAML
,
],
'nested mapping terminating at end of line' => [
[
'foo' => [
'bar' => 'foobar',
],
],
<<<YAML
{ foo: { bar: foobar }
}
YAML
,
],
'nested sequence terminating at end of line' => [
[
'foo',
[
'bar',
'baz',
],
],
<<<YAML
[ foo, [bar, baz]
]
YAML
],
'nested sequence spanning multiple lines' => [
[
['entry1', []],
['entry2'],
],
<<<YAML
[
['entry1', {}],
['entry2']
]
YAML
],
'sequence nested in mapping' => [
['foo' => ['bar', 'foobar'], 'bar' => ['baz']],
<<<YAML
@ -1698,6 +1755,22 @@ foobar: [foo,
YAML
,
],
'sequence spanning multiple lines nested in mapping with a following mapping' => [
[
'foobar' => [
'foo',
'bar',
],
'bar' => 'baz',
],
<<<YAML
foobar: [
foo,
bar,
]
bar: baz
YAML
],
'nested sequence nested in mapping starting on the same line' => [
[
'foo' => [
@ -1823,6 +1896,110 @@ YAML
foo: 'bar
baz'
YAML
],
'mixed mapping with inline notation having separated lines' => [
[
'map' => [
'key' => 'value',
'a' => 'b',
],
'param' => 'some',
],
<<<YAML
map: {
key: "value",
a: "b"
}
param: "some"
YAML
],
'mixed mapping with inline notation on one line' => [
[
'map' => [
'key' => 'value',
'a' => 'b',
],
'param' => 'some',
],
<<<YAML
map: {key: "value", a: "b"}
param: "some"
YAML
],
'mixed mapping with compact inline notation on one line' => [
[
'map' => [
'key' => 'value',
'a' => 'b',
],
'param' => 'some',
],
<<<YAML
map: {key: "value",
a: "b"}
param: "some"
YAML
],
'nested collections containing strings with bracket chars' => [
[
[']'],
['}'],
['ba[r'],
['[ba]r'],
['bar]'],
['foo' => 'bar{'],
['foo' => 'b{ar}'],
['foo' => 'bar}'],
],
<<<YAML
[
[
"]"
],
[
"}"
],
[
"ba[r"
],
[
'[ba]r'
],
[
"bar]"
],
{
foo: "bar{"
},
{
foo: "b{ar}"
},
{
foo: 'bar}'
}
]
YAML
],
'escaped characters in quoted strings' => [
[
['te"st'],
['test'],
["te'st"],
['te"st]'],
['te"st'],
['test'],
["te'st"],
['te"st]'],
],
<<<YAML
[
["te\"st"],["test"],['te''st'],["te\"st]"],
["te\"st"],
["test"],
['te''st'],
["te\"st]"]
]
YAML
],
];
@ -1846,6 +2023,13 @@ YAML;
$this->assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS));
}
public function testInvalidInlineSequenceContainingStringWithEscapedQuotationCharacter()
{
$this->expectException(ParseException::class);
$this->parser->parse('["\\"]');
}
/**
* @dataProvider taggedValuesProvider
*/