Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.0.0 #16

Merged
merged 82 commits into from
May 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
bb8b609
prepare for PHP 8
kodeart Aug 9, 2020
bd985a2
prepare for PHP 8
kodeart Aug 9, 2020
4e96ce6
prepare for PHP 8
kodeart Aug 9, 2020
9da95c2
- possible fix for PHP 8
kodeart Aug 14, 2020
3839352
- code style
kodeart Aug 14, 2020
d97e5f7
version bump
kodeart Aug 14, 2020
601aec9
- ensure the value is string
kodeart Aug 14, 2020
5b00004
- do not throw exception if header does not exist in the instance
kodeart Aug 24, 2020
bb34885
- exclude 'internet' tests for a time being
kodeart Sep 3, 2020
be384f5
- updates for new error changes in PHP 8
kodeart Sep 3, 2020
c5cbe97
- adds send() method in the interface
kodeart Sep 3, 2020
498d70d
- catches all exceptions and errors
kodeart Sep 3, 2020
af2a197
- access type for fopen() changed to binary
kodeart Sep 3, 2020
0da0916
- adds property types
kodeart Sep 4, 2020
4c15e3c
- adds property types
kodeart Sep 4, 2020
4c6dd0e
- adds property types
kodeart Sep 4, 2020
c115b35
- adds property types
kodeart Sep 4, 2020
60c8523
- adds property types
kodeart Sep 4, 2020
dc544f1
(temporary)
kodeart Sep 4, 2020
c9688df
- removed support for PHP 7.2
kodeart Sep 5, 2020
8d9cc8e
- Psr18Exception in its own file
kodeart Sep 5, 2020
bc5860e
- allow all php 7 versions to fail (prepare for v8.0)
kodeart Sep 5, 2020
fb5e0bf
CS
kodeart Sep 5, 2020
a13b058
CS
kodeart Sep 6, 2020
0b0b4ce
- methods renamed (create -> new) and (psr18 -> client)
kodeart Sep 6, 2020
dfb1b1c
- CS
kodeart Sep 7, 2020
1857dae
(temporary) updates for new error changes in PHP 8
kodeart Sep 7, 2020
e5466fa
(temporary) updates for new error changes in PHP 8
kodeart Sep 7, 2020
dc8bb3d
- CS
kodeart Sep 7, 2020
0324501
- CS
kodeart Sep 7, 2020
a5be25e
- do not convert anything to Exceptions
kodeart Sep 7, 2020
25cd094
- updates the tests for the http clients
kodeart Sep 7, 2020
705e073
- updates
kodeart Sep 7, 2020
809f240
- adds sendHeaders() and sendBody() methods
kodeart Sep 7, 2020
dcf6c7a
- always prepare the response object
kodeart Sep 9, 2020
c8f142d
- stream object is destroyed after send()
kodeart Sep 9, 2020
288c579
- standardize the error messages according to https://tools.ietf.org/…
kodeart Sep 9, 2020
56e5e47
- adds proper RFC-7807 response headers
kodeart Sep 9, 2020
f0552f4
- match() can return all matched headers
kodeart Sep 10, 2020
1676057
- matches() can return all matched headers
kodeart Sep 10, 2020
11646db
- matches() can return all matched headers
kodeart Sep 10, 2020
ba2937e
- trim the sub/types
kodeart Sep 10, 2020
9b75fe8
- test
kodeart Sep 10, 2020
98afddf
- test
kodeart Sep 10, 2020
bb3c1a4
- prepare for PHP 8
kodeart Sep 14, 2020
6a6ead7
- http validator expects a POST body
kodeart Sep 14, 2020
7caaf04
- http validator signature changed (pass the input payload as reference)
kodeart Sep 14, 2020
4b6d80c
Uri class is now JSON serializable
kodeart Sep 27, 2020
86105c1
- fixes the curl client JSON error message
kodeart Oct 3, 2020
21ae7d0
- updated PHP version requirement
kodeart Oct 22, 2020
92f60ce
Update for PHP 8
kodeart Dec 1, 2020
48afd7c
Update for PHP 8
kodeart Dec 1, 2020
b80e180
Update for PHP 8
kodeart Dec 1, 2020
161040a
- WIP: content negotiator
kodeart Dec 6, 2020
e16da66
- use phpunit 8
kodeart Dec 6, 2020
e2ed18f
- use phpunit 8
kodeart Dec 6, 2020
2ff3621
- removes curl_close(), has no effect in PHP 8
kodeart Mar 7, 2021
635bb0e
- fixes the ranking (subtype instead of type)
kodeart Mar 22, 2021
82e8cc2
- updates for default browser Accept header (Firefox and Chromium)
kodeart Mar 22, 2021
c941609
- chore: better readability and cleanup
kodeart Apr 1, 2021
3fd6a7f
- wip: test ranking for catch-all (expects header)
kodeart Apr 1, 2021
4ecdc90
- chore: cleanup
kodeart Apr 2, 2021
e9bf422
- PHP 8 improvements
kodeart Apr 2, 2021
8b18946
- update
kodeart Apr 2, 2021
8ae981d
- using PhpUnit 9
kodeart Apr 2, 2021
6c9562b
- namespace changed for all unit tests
kodeart Apr 2, 2021
b308a8e
- PHP 8 improvements
kodeart Apr 2, 2021
41ea3b1
- year bump
kodeart Apr 2, 2021
d6f6f9f
- added a consistent error response for clients (RFC-7807)
kodeart Apr 2, 2021
6a542ab
- stream_to_string read buffer lowered to 64KB
kodeart Apr 2, 2021
0906b0e
- lots of improvements of the header methods
kodeart Apr 2, 2021
3515954
- uses static return type
kodeart Apr 2, 2021
f0c093c
- updates
kodeart Apr 2, 2021
5bace7d
- updates
kodeart Apr 2, 2021
6981d08
- PHP 8 improvements
kodeart Apr 2, 2021
abfb2c0
- HEAD and OPTIONS methods creates an empty payload
kodeart Apr 2, 2021
fa218d4
- updates for latest stdlib changes
kodeart May 3, 2021
9d02378
- phpunit v9 config update
kodeart May 3, 2021
075e4da
- up from PHP 8.0.1
kodeart May 3, 2021
80ddeb3
- use published version (stdlib)
kodeart May 3, 2021
1a8e037
- force native functions
kodeart May 3, 2021
e4dcc76
Merge branch 'master' into v3
kodeart May 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ vendor
.idea
.tmp
composer.lock
*.cache
2 changes: 1 addition & 1 deletion .scrutinizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build:
- php-scrutinizer-run
environment:
php:
version: '7.3'
version: '8.0.1'

before_commands:
- 'composer update -o --prefer-source --no-interaction'
Expand Down
8 changes: 3 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
language: php
os: linux
dist: xenial
dist: bionic

notifications:
email: false

php:
- 7.2
- 7.3
- 7.4
- 8.0.1
- nightly

cache:
Expand All @@ -27,7 +25,7 @@ install:
- composer update -o --prefer-source --no-interaction

script:
- vendor/bin/phpunit --coverage-clover build/clover.xml
- vendor/bin/phpunit --exclude-group internet --coverage-clover build/clover.xml

after_script:
- wget https://scrutinizer-ci.com/ocular.phar
Expand Down
152 changes: 61 additions & 91 deletions AcceptHeaderNegotiator.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,56 +25,52 @@
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation
*/

use Generator;
use InvalidArgumentException;
use Koded\Http\Interfaces\HttpStatus;

class AcceptHeaderNegotiator
{
/** @var AcceptHeader[] */
private $supports;
private string $supports = '';

public function __construct(string $supportHeader)
{
$this->supports = $supportHeader;
}


public function match(string $accepts): AcceptHeader
{
return $this->matches($accepts)[0];
}

public function matches(string $accepts): array
{
/** @var AcceptHeader $support */
foreach ($this->parse($accepts) as $accept) {
foreach ($this->parse($this->supports) as $support) {
$support->matches($accept, $types);
$support->matches($accept, $matches);
}
}

usort($types, function(AcceptHeader $a, AcceptHeader $b) {
return $b->weight() <=> $a->weight();
});

if (empty($types)) {
usort($matches, fn(AcceptHeader $a, AcceptHeader $b) => $b->weight() <=> $a->weight());
if (empty($matches)) {
/* Set "q=0", meaning the header is explicitly rejected.
* The consuming clients should handle this according to
* their internal logic. This is much better then throwing
* exceptions which must be handled in every place where
* match() is called. For example, the client may issue a
* 406 status code and be done with it.
*/
$types[] = new class('*;q=0') extends AcceptHeader {};
$matches[] = new class('*;q=0') extends AcceptHeader {};
}

return $types[0];
return $matches;
}

/**
* @param string $header
*
* @return Generator
* @return \Generator
*/
private function parse(string $header): Generator
private function parse(string $header): \Generator
{
foreach (explode(',', $header) as $header) {
foreach (\explode(',', $header) as $header) {
yield new class($header) extends AcceptHeader {};
}
}
Expand All @@ -83,177 +79,151 @@ private function parse(string $header): Generator

abstract class AcceptHeader
{
private $header;
private $separator;
private $type;
private $subtype;
private $quality = 1.0;
private $weight = 0.0;
private $catchAll = false;
private $params = [];
private string $header = '';
private string $separator = '/';
private string $type = '';
private string $subtype = '*';
private float $quality = 1.0;
private float $weight = 0.0;
private bool $catchAll = false;
private array $params = [];

public function __construct(string $header)
{
$this->header = $header;

$header = preg_replace('/[[:space:]]/', '', $header);
$bits = explode(';', $header);
$type = array_shift($bits);

if (!empty($type) && !preg_match('~^(\*|[a-z0-9._]+)([/|_-])?(\*|[a-z0-9.\-_+]+)?$~i', $type, $matches)) {
throw new InvalidArgumentException(sprintf('"%s" is not a valid Access header', $header),
$header = \preg_replace('/[[:space:]]/', '', $header);
$bits = \explode(';', $header);
$type = \array_shift($bits);
if (!empty($type) && !\preg_match('~^(\*|[a-z0-9._]+)([/|_\-])?(\*|[a-z0-9.\-_+]+)?$~i', $type, $matches)) {
throw new \InvalidArgumentException(\sprintf('"%s" is not a valid Access header', $header),
HttpStatus::NOT_ACCEPTABLE);
}

$this->separator = $matches[2] ?? '/';
[$type, $subtype] = explode($this->separator, $type, 2) + [1 => '*'];

[$type, $subtype] = \explode($this->separator, $type, 2) + [1 => '*'];
if ('*' === $type && '*' !== $subtype) {
// @see https://tools.ietf.org/html/rfc7231#section-5.3.2
throw new InvalidArgumentException(sprintf('"%s" is not a valid Access header', $header),
throw new \InvalidArgumentException(\sprintf('"%s" is not a valid Access header', $header),
HttpStatus::NOT_ACCEPTABLE);
}

// @see https://tools.ietf.org/html/rfc7540#section-8.1.2
$this->type = strtolower($type);

/* Uses a simple heuristic to check if subtype is part of
* some obscure media type like "vnd.api-v1+json".
$this->type = \trim(\strtolower($type));
/*
* Uses a simple heuristic to check if subtype is part of
* some convoluted media type like "vnd.api-v1+json".
*
* NOTE: It is a waste of time to negotiate on the basis
* of obscure parameters while using a meaningless media
* type like "vnd.whatever". But the web world is a big mess
* and this module can handle the Dunning-Kruger effect.
* type like "vnd.whatever". The web world is a big mess
* but this module can handle the Dunning-Kruger effect.
*/
$this->subtype = explode('+', $subtype)[1] ?? $subtype;
$this->catchAll = '*' === $this->type && '*' === $this->subtype;

parse_str(join('&', $bits), $this->params);
$this->subtype = \trim(\explode('+', $subtype)[1] ?? $subtype);
$this->catchAll = ('*' === $this->type) && ('*' === $this->subtype);
\parse_str(\join('&', $bits), $this->params);
$this->quality = (float)($this->params['q'] ?? 1);
unset($this->params['q']);
}


public function __toString(): string
{
return $this->value();
}


public function value(): string
{
// The header is explicitly rejected
if (0.0 === $this->quality) {
$this->type = $this->subtype = '';
return '';
}

// If language, encoding or charset
if ('*' === $this->subtype) {
return $this->type;
}

return $this->type . $this->separator . $this->subtype;
}


public function quality(): float
{
return $this->quality;
}


public function weight(): float
{
return $this->weight;
}

public function is(string $type): bool
{
return ($type === $this->subtype) && ($this->subtype !== '*');
}

/**
* @internal
*
* @param AcceptHeader $accept The accept header part
* @param AcceptHeader[] $matches Matched types
*
* @return bool TRUE if the accept header part is a match
* against the supported (this) header part
*
* This method finds the best match for the Accept header,
* including all the nonsense that may be passed by the
* including lots of nonsense that may be passed by the
* developers who do not follow RFC standards.
*
* @internal
*/
public function matches(AcceptHeader $accept, array &$matches = null): bool
public function matches(AcceptHeader $accept, array &$matches = null): void
{
$matches = (array)$matches;
$accept = clone $accept;

$typeMatch = $this->type === $accept->type;

$matches = (array)$matches;
$accept = clone $accept;
$typeMatch = ($this->type === $accept->type);
if (1.0 === $accept->quality) {
$accept->quality = (float)$this->quality;
}

if ($accept->catchAll) {
$accept->type = $this->type;
$accept->subtype = $this->subtype;
$matches[] = $accept;

return true;
return;
}

// Explicitly denied
if (0.0 === $this->quality) {
$matches[] = clone $this;

return true;
return;
}

// Explicitly denied
if (0.0 === $accept->quality) {
$matches[] = $accept;

return true;
return;
}

// Explicit type mismatch (w/o asterisk); bail out
if (false === $typeMatch && '*' !== $this->type) {
return false;
if ((false === $typeMatch) && ('*' !== $this->type)) {
return;
}

if ('*' === $accept->subtype) {
$accept->subtype = $this->subtype;
}

if ($accept->subtype !== $this->subtype && '*' !== $this->subtype) {
return false;
if (($accept->subtype !== $this->subtype) && ('*' !== $this->subtype)) {
return;
}

$matches[] = $this->rank($accept);

return true;
}


private function rank(AcceptHeader $accept): AcceptHeader
{
// +100 if types are exact match w/o asterisk
if ($this->type === $accept->type && '*' !== $accept->type) {
if (($this->type === $accept->type) &&
($this->subtype === $accept->subtype)) {
$accept->weight += 100;
}

$accept->weight += $this->catchAll ? 0.0 : $accept->quality;

$accept->weight += ($this->catchAll ? 0.0 : $accept->quality);
// +1 for each parameter that matches, except "q"
foreach ($this->params as $k => $v) {
if (isset($accept->params[$k]) && $accept->params[$k] === $v) {
if (isset($accept->params[$k]) && ($accept->params[$k] === $v)) {
$accept->weight += 1;
} else {
$accept->weight -= 1;
}
}

// Add "q"
$accept->weight += $accept->quality;

return $accept;
}
}
Loading