first commit

This commit is contained in:
root
2020-02-19 16:42:35 +01:00
commit d668d90f82
2224 changed files with 334338 additions and 0 deletions

View File

@ -0,0 +1,33 @@
Contributing
============
First of all, **thank you** for contributing, **you are awesome**!
Here are a few rules to follow in order to ease code reviews, and discussions before
maintainers accept and merge your work.
You MUST follow the [PSR-1](http://www.php-fig.org/psr/1/) and
[PSR-2](http://www.php-fig.org/psr/2/). If you don't know about any of them, you
should really read the recommendations. Can't wait? Use the [PHP-CS-Fixer
tool](http://cs.sensiolabs.org/).
You MUST run the test suite.
You MUST write (or update) unit tests.
You SHOULD write documentation.
Please, write [commit messages that make
sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html),
and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing)
before submitting your Pull Request.
One may ask you to [squash your
commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)
too. This is used to "clean" your Pull Request before merging it (we don't want
commits such as `fix tests`, `fix 2`, `fix 3`, etc.).
Also, while creating your Pull Request on GitHub, you MUST write a description
which gives the context and/or explains why you are creating it.
Thank you!

19
vendor/willdurand/negotiation/LICENSE vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) William Durand <will+git@drnd.me>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

162
vendor/willdurand/negotiation/README.md vendored Normal file
View File

@ -0,0 +1,162 @@
Negotiation
===========
[![Build
Status](https://travis-ci.org/willdurand/Negotiation.png?branch=master)](http://travis-ci.org/willdurand/Negotiation)
[![Build
status](https://ci.appveyor.com/api/projects/status/6tbe8j3gofdlfm4v?svg=true)](https://ci.appveyor.com/project/willdurand/negotiation)
[![Total
Downloads](https://poser.pugx.org/willdurand/Negotiation/downloads.png)](https://packagist.org/packages/willdurand/Negotiation)
[![Latest Stable
Version](https://poser.pugx.org/willdurand/Negotiation/v/stable.png)](https://packagist.org/packages/willdurand/Negotiation)
![PHP 7 ready](https://img.shields.io/badge/PHP%207-ready-green.svg)
**Negotiation** is a standalone library without any dependencies that allows you
to implement [content
negotiation](https://tools.ietf.org/html/rfc7231#section-5.3) in your
application, whatever framework you use. This library is based on [RFC
7231](https://tools.ietf.org/html/rfc7231). Negotiation is easy to use, and
extensively unit tested!
> **Important:** You are browsing the documentation of Negotiation **2.x**.
Documentation for version **1.x** is available here: [Negotiation 1.x
documentation](https://github.com/willdurand/Negotiation/blob/1.x/README.md#usage).
You might also be interested in this: [**What's new in Negotiation 2?**](https://github.com/willdurand/Negotiation/releases/tag/v2.0.0-alpha1)
Installation
------------
The recommended way to install Negotiation is through
[Composer](http://getcomposer.org/):
```bash
$ composer require willdurand/negotiation
```
Usage Examples
--------------
### Media Type Negotiation
``` php
$negotiator = new \Negotiation\Negotiator();
$acceptHeader = 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8';
$priorities = array('text/html; charset=UTF-8', 'application/json', 'application/xml;q=0.5');
$mediaType = $negotiator->getBest($acceptHeader, $priorities);
$value = $mediaType->getValue();
// $value == 'text/html; charset=UTF-8'
```
The `Negotiator` returns an instance of `Accept`, or `null` if negotiating the
best media type has failed.
### Language Negotiation
``` php
<?php
$negotiator = new \Negotiation\LanguageNegotiator();
$acceptLangageHeader = 'en; q=0.1, fr; q=0.4, fu; q=0.9, de; q=0.2';
$priorities = array('de', 'fu', 'en');
$bestLanguage = $negotiator->getBest($acceptLangageHeader, $priorities);
$type = $bestLanguage->getType();
// $type == 'fu';
$quality = $bestLanguage->getQuality();
// $quality == 0.9
```
The `LanguageNegotiator` returns an instance of `AcceptLanguage`.
### Encoding Negotiation
``` php
<?php
$negotiator = new \Negotiation\EncodingNegotiator();
$encoding = $negotiator->getBest($acceptHeader, $priorities);
```
The `EncodingNegotiator` returns an instance of `AcceptEncoding`.
### Charset Negotiation
``` php
<?php
$negotiator = new \Negotiation\CharsetNegotiator();
$acceptCharsetHeader = 'ISO-8859-1, UTF-8; q=0.9';
$priorities = array('iso-8859-1;q=0.3', 'utf-8;q=0.9', 'utf-16;q=1.0');
$bestCharset = $negotiator->getBest($acceptCharsetHeader, $priorities);
$type = $bestCharset->getType();
// $type == 'utf-8';
$quality = $bestCharset->getQuality();
// $quality == 0.81
```
The `CharsetNegotiator` returns an instance of `AcceptCharset`.
### `Accept*` Classes
`Accept` and `Accept*` classes share common methods such as:
* `getValue()` returns the accept value (e.g. `text/html; z=y; a=b; c=d`)
* `getNormalizedValue()` returns the value with parameters sorted (e.g.
`text/html; a=b; c=d; z=y`)
* `getQuality()` returns the quality if available (`q` parameter)
* `getType()` returns the accept type (e.g. `text/html`)
* `getParameters()` returns the set of parameters (excluding the `q` parameter
if provided)
* `getParameter()` allows to retrieve a given parameter by its name. Fallback to
a `$default` (nullable) value otherwise.
* `hasParameter()` indicates whether a parameter exists.
Unit Tests
----------
Setup the test suite using Composer:
$ composer install --dev
Run it using PHPUnit:
$ phpunit
Contributing
------------
See [CONTRIBUTING](CONTRIBUTING.md) file.
Credits
-------
* Some parts of this library are inspired by:
* [Symfony](http://github.com/symfony/symfony) framework;
* [FOSRest](http://github.com/FriendsOfSymfony/FOSRest);
* [PEAR HTTP2](https://github.com/pear/HTTP2).
* William Durand <will+git@drnd.me>
* [@neural-wetware](https://github.com/neural-wetware)
License
-------
Negotiation is released under the MIT License. See the bundled LICENSE file for
details.

View File

@ -0,0 +1,118 @@
<?php
namespace Negotiation;
use Negotiation\Exception\InvalidArgument;
use Negotiation\Exception\InvalidHeader;
abstract class AbstractNegotiator
{
/**
* @param string $header A string containing an `Accept|Accept-*` header.
* @param array $priorities A set of server priorities.
*
* @return AcceptHeader|null best matching type
*/
public function getBest($header, array $priorities)
{
if (empty($priorities)) {
throw new InvalidArgument('A set of server priorities should be given.');
}
if (!$header) {
throw new InvalidArgument('The header string should not be empty.');
}
// Once upon a time, two `array_map` calls were sitting there, but for
// some reasons, they triggered `E_WARNING` time to time (because of
// PHP bug [55416](https://bugs.php.net/bug.php?id=55416). Now, they
// are gone.
// See: https://github.com/willdurand/Negotiation/issues/81
$acceptedHeaders = array();
foreach ($this->parseHeader($header) as $h) {
try {
$acceptedHeaders[] = $this->acceptFactory($h);
} catch (Exception\Exception $e) {
// silently skip in case of invalid headers coming in from a client
}
}
$acceptedPriorities = array();
foreach ($priorities as $p) {
$acceptedPriorities[] = $this->acceptFactory($p);
}
$matches = $this->findMatches($acceptedHeaders, $acceptedPriorities);
$specificMatches = array_reduce($matches, 'Negotiation\Match::reduce', []);
usort($specificMatches, 'Negotiation\Match::compare');
$match = array_shift($specificMatches);
return null === $match ? null : $acceptedPriorities[$match->index];
}
/**
* @param string $header accept header part or server priority
*
* @return AcceptHeader Parsed header object
*/
abstract protected function acceptFactory($header);
/**
* @param AcceptHeader $header
* @param AcceptHeader $priority
* @param integer $index
*
* @return Match|null Headers matched
*/
protected function match(AcceptHeader $header, AcceptHeader $priority, $index)
{
$ac = $header->getType();
$pc = $priority->getType();
$equal = !strcasecmp($ac, $pc);
if ($equal || $ac === '*') {
$score = 1 * $equal;
return new Match($header->getQuality() * $priority->getQuality(), $score, $index);
}
return null;
}
/**
* @param string $header A string that contains an `Accept*` header.
*
* @return AcceptHeader[]
*/
private function parseHeader($header)
{
$res = preg_match_all('/(?:[^,"]*+(?:"[^"]*+")?)+[^,"]*+/', $header, $matches);
if (!$res) {
throw new InvalidHeader(sprintf('Failed to parse accept header: "%s"', $header));
}
return array_values(array_filter(array_map('trim', $matches[0])));
}
/**
* @param AcceptHeader[] $headerParts
* @param Priority[] $priorities Configured priorities
*
* @return Match[] Headers matched
*/
private function findMatches(array $headerParts, array $priorities)
{
$matches = [];
foreach ($priorities as $index => $p) {
foreach ($headerParts as $h) {
if (null !== $match = $this->match($h, $p, $index)) {
$matches[] = $match;
}
}
}
return $matches;
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Negotiation;
use Negotiation\Exception\InvalidMediaType;
final class Accept extends BaseAccept implements AcceptHeader
{
private $basePart;
private $subPart;
public function __construct($value)
{
parent::__construct($value);
if ($this->type === '*') {
$this->type = '*/*';
}
$parts = explode('/', $this->type);
if (count($parts) !== 2 || !$parts[0] || !$parts[1]) {
throw new InvalidMediaType();
}
$this->basePart = $parts[0];
$this->subPart = $parts[1];
}
/**
* @return string
*/
public function getSubPart()
{
return $this->subPart;
}
/**
* @return string
*/
public function getBasePart()
{
return $this->basePart;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation;
final class AcceptCharset extends BaseAccept implements AcceptHeader
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation;
final class AcceptEncoding extends BaseAccept implements AcceptHeader
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation;
interface AcceptHeader
{
}

View File

@ -0,0 +1,49 @@
<?php
namespace Negotiation;
use Negotiation\Exception\InvalidLanguage;
final class AcceptLanguage extends BaseAccept implements AcceptHeader
{
private $language;
private $script;
private $region;
public function __construct($value)
{
parent::__construct($value);
$parts = explode('-', $this->type);
if (2 === count($parts)) {
$this->language = $parts[0];
$this->region = $parts[1];
} elseif (1 === count($parts)) {
$this->language = $parts[0];
} elseif (3 === count($parts)) {
$this->language = $parts[0];
$this->script = $parts[1];
$this->region = $parts[2];
} else {
// TODO: this part is never reached...
throw new InvalidLanguage();
}
}
/**
* @return string
*/
public function getSubPart()
{
return $this->region;
}
/**
* @return string
*/
public function getBasePart()
{
return $this->language;
}
}

View File

@ -0,0 +1,154 @@
<?php
namespace Negotiation;
abstract class BaseAccept
{
/**
* @var float
*/
private $quality = 1.0;
/**
* @var string
*/
private $normalized;
/**
* @var string
*/
private $value;
/**
* @var array
*/
private $parameters;
/**
* @var string
*/
protected $type;
/**
* @param string $value
*/
public function __construct($value)
{
list($type, $parameters) = $this->parseParameters($value);
if (isset($parameters['q'])) {
$this->quality = (float) $parameters['q'];
unset($parameters['q']);
}
$type = trim(strtolower($type));
$this->value = $value;
$this->normalized = $type . ($parameters ? "; " . $this->buildParametersString($parameters) : '');
$this->type = $type;
$this->parameters = $parameters;
}
/**
* @return string
*/
public function getNormalizedValue()
{
return $this->normalized;
}
/**
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @return float
*/
public function getQuality()
{
return $this->quality;
}
/**
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* @param string $key
* @param mixed $default
*
* @return string|null
*/
public function getParameter($key, $default = null)
{
return isset($this->parameters[$key]) ? $this->parameters[$key] : $default;
}
/**
* @param string $key
*
* @return boolean
*/
public function hasParameter($key)
{
return isset($this->parameters[$key]);
}
/**
*
* @param string $acceptPart
* @return array
*/
private function parseParameters($acceptPart)
{
$parts = explode(';', $acceptPart);
$type = array_shift($parts);
$parameters = [];
foreach ($parts as $part) {
$part = explode('=', $part);
if (2 !== count($part)) {
continue; // TODO: throw exception here?
}
$key = strtolower(trim($part[0])); // TODO: technically not allowed space around "=". throw exception?
$parameters[$key] = trim($part[1], ' "');
}
return [ $type, $parameters ];
}
/**
* @param string $parameters
*
* @return string
*/
private function buildParametersString($parameters)
{
$parts = [];
ksort($parameters);
foreach ($parameters as $key => $val) {
$parts[] = sprintf('%s=%s', $key, $val);
}
return implode('; ', $parts);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Negotiation;
class CharsetNegotiator extends AbstractNegotiator
{
/**
* {@inheritdoc}
*/
protected function acceptFactory($accept)
{
return new AcceptCharset($accept);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Negotiation;
class EncodingNegotiator extends AbstractNegotiator
{
/**
* {@inheritdoc}
*/
protected function acceptFactory($accept)
{
return new AcceptEncoding($accept);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation\Exception;
interface Exception
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation\Exception;
class InvalidArgument extends \InvalidArgumentException implements Exception
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation\Exception;
class InvalidHeader extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation\Exception;
class InvalidLanguage extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Negotiation\Exception;
class InvalidMediaType extends \RuntimeException implements Exception
{
}

View File

@ -0,0 +1,41 @@
<?php
namespace Negotiation;
class LanguageNegotiator extends AbstractNegotiator
{
/**
* {@inheritdoc}
*/
protected function acceptFactory($accept)
{
return new AcceptLanguage($accept);
}
/**
* {@inheritdoc}
*/
protected function match(AcceptHeader $acceptLanguage, AcceptHeader $priority, $index)
{
if (!$acceptLanguage instanceof AcceptLanguage || !$priority instanceof AcceptLanguage) {
return null;
}
$ab = $acceptLanguage->getBasePart();
$pb = $priority->getBasePart();
$as = $acceptLanguage->getSubPart();
$ps = $priority->getSubPart();
$baseEqual = !strcasecmp($ab, $pb);
$subEqual = !strcasecmp($as, $ps);
if (($ab == '*' || $baseEqual) && ($as === null || $subEqual)) {
$score = 10 * $baseEqual + $subEqual;
return new Match($acceptLanguage->getQuality() * $priority->getQuality(), $score, $index);
}
return null;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace Negotiation;
final class Match
{
/**
* @var float
*/
public $quality;
/**
* @var int
*/
public $score;
/**
* @var int
*/
public $index;
public function __construct($quality, $score, $index)
{
$this->quality = $quality;
$this->score = $score;
$this->index = $index;
}
/**
* @param Match $a
* @param Match $b
*
* @return int
*/
public static function compare(Match $a, Match $b)
{
if ($a->quality !== $b->quality) {
return $a->quality > $b->quality ? -1 : 1;
}
if ($a->index !== $b->index) {
return $a->index > $b->index ? 1 : -1;
}
return 0;
}
/**
* @param array $carry reduced array
* @param Match $match match to be reduced
*
* @return Match[]
*/
public static function reduce(array $carry, Match $match)
{
if (!isset($carry[$match->index]) || $carry[$match->index]->score < $match->score) {
$carry[$match->index] = $match;
}
return $carry;
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace Negotiation;
class Negotiator extends AbstractNegotiator
{
/**
* {@inheritdoc}
*/
protected function acceptFactory($accept)
{
return new Accept($accept);
}
/**
* {@inheritdoc}
*/
protected function match(AcceptHeader $accept, AcceptHeader $priority, $index)
{
if (!$accept instanceof Accept || !$priority instanceof Accept) {
return null;
}
$acceptBase = $accept->getBasePart();
$priorityBase = $priority->getBasePart();
$acceptSub = $accept->getSubPart();
$prioritySub = $priority->getSubPart();
$intersection = array_intersect_assoc($accept->getParameters(), $priority->getParameters());
$baseEqual = !strcasecmp($acceptBase, $priorityBase);
$subEqual = !strcasecmp($acceptSub, $prioritySub);
if (($acceptBase === '*' || $baseEqual)
&& ($acceptSub === '*' || $subEqual)
&& count($intersection) === count($accept->getParameters())
) {
$score = 100 * $baseEqual + 10 * $subEqual + count($intersection);
return new Match($accept->getQuality() * $priority->getQuality(), $score, $index);
}
if (!strstr($acceptSub, '+') || !strstr($prioritySub, '+')) {
return null;
}
// Handle "+" segment wildcards
list($acceptSub, $acceptPlus) = $this->splitSubPart($acceptSub);
list($prioritySub, $priorityPlus) = $this->splitSubPart($prioritySub);
// If no wildcards in either the subtype or + segment, do nothing.
if (!($acceptBase === '*' || $baseEqual)
|| !($acceptSub === '*' || $prioritySub === '*' || $acceptPlus === '*' || $priorityPlus === '*')
) {
return null;
}
$subEqual = !strcasecmp($acceptSub, $prioritySub);
$plusEqual = !strcasecmp($acceptPlus, $priorityPlus);
if (($acceptSub === '*' || $prioritySub === '*' || $subEqual)
&& ($acceptPlus === '*' || $priorityPlus === '*' || $plusEqual)
&& count($intersection) === count($accept->getParameters())
) {
$score = 100 * $baseEqual + 10 * $subEqual + $plusEqual + count($intersection);
return new Match($accept->getQuality() * $priority->getQuality(), $score, $index);
}
return null;
}
/**
* Split a subpart into the subpart and "plus" part.
*
* For media-types of the form "application/vnd.example+json", matching
* should allow wildcards for either the portion before the "+" or
* after. This method splits the subpart to allow such matching.
*/
protected function splitSubPart($subPart)
{
if (!strstr($subPart, '+')) {
return [$subPart, ''];
}
return explode('+', $subPart, 2);
}
}