diff --git a/.gitattributes b/.gitattributes index 05ed89a..93aa015 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,14 +1,14 @@ -* text=auto - # Always use LF core.autocrlf=lf -.editorconfig +.editorconfig export-ignore .gitattributes export-ignore .gitignore export-ignore -.php_cs export-ignore -.scrutinizer.yml export-ignore -.travis.yml export-ignore .github export-ignore -phpspec.yml export-ignore -/spec export-ignore +.php-cs-fixer.dist.php export-ignore +CODE_OF_CONDUCT.md export-ignore +Makefile export-ignore +phpunit.xml.dist export-ignore +phpstan.neon export-ignore +phpstan-baseline.neon export-ignore +tests/ export-ignore diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index eeaf12b..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,12 +0,0 @@ -| Q | A -| ---------------- | ----- -| Bug report? | yes/no -| Feature request? | yes/no -| BC Break report? | yes/no -| RFC? | yes/no - - diff --git a/.github/ISSUE_TEMPLATE/1_Bug_report.md b/.github/ISSUE_TEMPLATE/1_Bug_report.md new file mode 100644 index 0000000..1e26723 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_Bug_report.md @@ -0,0 +1,20 @@ +--- +name: "\U0001F41B Bug Report" +about: Report errors and problems +title: "[bug] " +labels: Potential Bug +assignees: '' + +--- + +**Description** + + +**How to reproduce** + + +**Possible Solution** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/2_Feature_request.md b/.github/ISSUE_TEMPLATE/2_Feature_request.md new file mode 100644 index 0000000..c21e708 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_Feature_request.md @@ -0,0 +1,15 @@ +--- +name: "\U0001F680 Feature Request" +about: "I have a suggestion (and may want to implement it \U0001F642)!" +title: "[Feature] " +labels: Feature +assignees: '' + +--- + +**Description** + + +**Example** + diff --git a/.github/ISSUE_TEMPLATE/3_Support_question.md b/.github/ISSUE_TEMPLATE/3_Support_question.md new file mode 100644 index 0000000..f933bf2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3_Support_question.md @@ -0,0 +1,13 @@ +--- +name: 👩‍🏫 Support Question +about: Questions about using this library +labels: Question / Support + +--- + +**Description** + diff --git a/.github/ISSUE_TEMPLATE/4_Security_issue.md b/.github/ISSUE_TEMPLATE/4_Security_issue.md new file mode 100644 index 0000000..ea75484 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4_Security_issue.md @@ -0,0 +1,14 @@ +--- +name: ⛔ Security Issue +about: Report security issues and problems (PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY) + +--- + +⚠ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW. + +If you have found a security issue in this project, please send the details to +security [at] rollerscapes.net and don't disclose it publicly until we can provide a +fix for it. + +**Note:** Please don't blindly send reports about automated tools, make sure the +reported issue is in fact exploitable. Thanks. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d38edd7..da9f029 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,12 +4,13 @@ | New feature? | yes/no | BC breaks? | yes/no | Deprecations? | yes/no -| Tests pass? | yes/no -| Fixed tickets | #... +| Fixed tickets | Fix #... | License | MIT diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..cb9a946 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,157 @@ +name: 'CI' + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + cs-fixer: + name: 'PHP CS Fixer' + + runs-on: 'ubuntu-latest' + + strategy: + matrix: + php-version: + - '8.2' + + steps: + - + name: 'Check out' + uses: 'actions/checkout@v4' + + - + name: 'Set up PHP' + uses: 'shivammathur/setup-php@v2' + with: + php-version: '${{ matrix.php-version }}' + coverage: 'none' + + - + name: 'Get Composer cache directory' + id: 'composer-cache' + run: 'echo "cache_dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT' + + - + name: 'Cache dependencies' + uses: 'actions/cache@v3' + with: + path: '${{ steps.composer-cache.outputs.cache_dir }}' + key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" + restore-keys: 'php-${{ matrix.php-version }}-composer-locked-' + + - + name: 'Install dependencies' + run: 'composer install --no-progress' + + - + name: 'Check the code style' + run: 'make cs' + + phpstan: + name: 'PhpStan' + + runs-on: 'ubuntu-latest' + + strategy: + matrix: + php-version: + - '8.2' + + steps: + - + name: 'Check out' + uses: 'actions/checkout@v4' + + - + name: 'Set up PHP' + uses: 'shivammathur/setup-php@v2' + with: + php-version: '${{ matrix.php-version }}' + coverage: 'none' + + - + name: 'Get Composer cache directory' + id: 'composer-cache' + run: 'echo "cache_dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT' + + - + name: 'Cache dependencies' + uses: 'actions/cache@v3' + with: + path: '${{ steps.composer-cache.outputs.cache_dir }}' + key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" + restore-keys: 'php-${{ matrix.php-version }}-composer-locked-' + + - + name: 'Install dependencies' + run: 'composer install --no-progress' + + - + name: 'Run PhpStan' + run: 'vendor/bin/phpstan analyze --no-progress' + + tests: + name: 'PHPUnit' + + runs-on: 'ubuntu-latest' + + strategy: + matrix: + include: + - + php-version: '8.1' + composer-options: '--prefer-stable' + symfony-version: '6.3' + - + php-version: '8.2' + composer-options: '--prefer-stable' + symfony-version: '^6.4' + + - + php-version: '8.2' + composer-options: '--prefer-stable' + symfony-version: '^7.0' + + steps: + - + name: 'Check out' + uses: 'actions/checkout@v4' + + - + name: 'Set up PHP' + uses: 'shivammathur/setup-php@v2' + with: + php-version: '${{ matrix.php-version }}' + coverage: 'none' + + - + name: 'Get Composer cache directory' + id: 'composer-cache' + run: 'echo "cache_dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT' + + - + name: 'Cache dependencies' + uses: 'actions/cache@v3' + with: + path: '${{ steps.composer-cache.outputs.cache_dir }}' + key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" + restore-keys: 'php-${{ matrix.php-version }}-composer-locked-' + + - + name: 'Install dependencies' + env: + COMPOSER_OPTIONS: '${{ matrix.composer-options }}' + SYMFONY_REQUIRE: '${{ matrix.symfony-version }}' + run: | + composer global config --no-plugins allow-plugins.symfony/flex true + composer global require --no-progress --no-scripts --no-plugins symfony/flex + composer update --no-progress $COMPOSER_OPTIONS + + - + name: 'Run tests' + run: make phpunit diff --git a/.gitignore b/.gitignore index 2fc0c2f..e2245b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ -/composer.lock -*.phar +composer.lock /vendor/ -.php_cs.cache + +phpunit.xml +.phpunit.result.cache +.phpunit.cache/ +.phpunit + +.php-cs-fixer.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..f90f35c --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,30 @@ + + +This source file is subject to the MIT license that is bundled +with this source code in the file LICENSE. +EOF; + +/** @var \Symfony\Component\Finder\Finder $finder */ +$finder = PhpCsFixer\Finder::create(); +$finder + ->in([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]); + +$config = new PhpCsFixer\Config(); +$config + ->setRiskyAllowed(true) + ->setRules( + array_merge( + require __DIR__ . '/vendor/rollerscapes/standards/php-cs-fixer-rules.php', + ['header_comment' => ['header' => $header]]) + ) + ->setFinder($finder); + +return $config; diff --git a/.php_cs b/.php_cs deleted file mode 100644 index 4bc6030..0000000 --- a/.php_cs +++ /dev/null @@ -1,48 +0,0 @@ - - -This source file is subject to the MIT license that is bundled -with this source code in the file LICENSE. -EOF; - -return PhpCsFixer\Config::create() - ->setRules([ - '@Symfony' => true, - '@Symfony:risky' => true, - '@PHP70Migration' => true, - '@PHP71Migration' => true, - 'array_syntax' => array('syntax' => 'short'), - 'combine_consecutive_unsets' => true, - 'declare_strict_types' => true, - 'header_comment' => ['header' => $header], - 'heredoc_to_nowdoc' => true, - 'linebreak_after_opening_tag' => true, - 'no_extra_consecutive_blank_lines' => ['continue', 'extra', 'return', 'throw', 'use', 'parenthesis_brace_block', 'square_brace_block', 'curly_brace_block'], - 'no_short_echo_tag' => true, - 'no_unreachable_default_argument_value' => false, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'ordered_class_elements' => false, - 'ordered_imports' => true, - 'phpdoc_add_missing_param_annotation' => false, - 'phpdoc_annotation_without_dot' => true, - 'phpdoc_no_empty_return' => false, // PHP 7 compatibility - 'phpdoc_order' => true, - // This breaks for variable @var blocks - 'phpdoc_to_comment' => false, - 'phpdoc_var_without_name' => false, - 'semicolon_after_instruction' => true, - 'single_import_per_statement' => false, - 'strict_comparison' => false, - 'strict_param' => true, - ]) - ->setRiskyAllowed(true) - ->setFinder( - PhpCsFixer\Finder::create() - ->in([__DIR__.'/src', __DIR__.'/spec']) - ) -; diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 114ee4c..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,26 +0,0 @@ -filter: - paths: [ 'src/*' ] - -checks: - php: - code_rating: true - duplication: false - verify_property_names: true - uppercase_constants: true - remove_extra_empty_lines: true - properties_in_camelcaps: true - parameters_in_camelcaps: true - overriding_parameter: true - optional_parameters_at_the_end: true - function_in_camel_caps: true - encourage_single_quotes: true - classes_in_camel_caps: true - check_method_contracts: - verify_interface_like_constraints: true - verify_documented_constraints: true - verify_parent_constraints: true - avoid_perl_style_comments: true - avoid_usage_of_logical_operators: true - no_exit: false - no_unnecessary_final_modifier: false - overriding_private_members: false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4edf236..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: php - -sudo: false - -branches: - only: - - master - -matrix: - include: - - php: 7.1 - fast_finish: true - -cache: - directories: - - $HOME/.composer/cache - -before_install: - - phpenv config-rm xdebug.ini || echo "xdebug not available" - -install: - - composer install -o - -script: - - vendor/bin/phpspec run diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 76f31b0..3bd3991 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1 +1 @@ -This project's code-of-conduct can be found at https://github.com/rollerworks/contributing/blob/master/CODE_OF_CONDUCT.md +This project's code-of-conduct can be found at https://contributing.rollerscapes.net/latest/code-of-conduct diff --git a/LICENSE b/LICENSE index da6f27c..90816ab 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2018 Sebastiaan Stok +Copyright (c) 2014 Sebastiaan Stok Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2ba1948 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +include vendor/rollerscapes/standards/Makefile + +phpunit: + ./vendor/bin/phpunit diff --git a/README.md b/README.md index 5050608..c59ae3e 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,8 @@ -Rollerworks UriEncoder Component -================================ +Rollerworks UriEncoder +====================== -[![Build Status](https://secure.travis-ci.org/rollerworks/rollerworks-uri-encoder.png?branch=master)](http://travis-ci.org/rollerworks/rollerworks-uri-encoder) -[![SensioLabsInsight](https://insight.sensiolabs.com/projects/0b197295-cc98-4425-afe6-ad2b59283db6/mini.png)](https://insight.sensiolabs.com/projects/0b197295-cc98-4425-afe6-ad2b59283db6) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/rollerworks/rollerworks-uri-encoder/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/rollerworks/rollerworks-uri-encoder/?branch=master) - -This package provides the Rollerworks UriEncoder component, -a simple library, to safely encode a string for usage in a URI. - -And some minor extra's, like string compression and conversion caching. +This package provides the Rollerworks UriEncoder component, a simple library, +to safely encode a string for usage in a URI. Plus a zlib compression. **Caution:** @@ -18,44 +12,38 @@ And some minor extra's, like string compression and conversion caching. > Use [paragonie/constant_time_encoding](https://github.com/paragonie/constant_time_encoding) > for time-safe en/decoding. Don't use conversion caching or compression for sensitive information! -Installation ------------- +## Installation -To install this package, add `rollerworks/search-uri-encoder` to your composer.json +To install this package, add `rollerworks/search-uri-encoder` to your composer.json: ```bash $ php composer.phar require rollerworks/search-uri-encoder ``` -Now, Composer will automatically download all required files, and install them -for you. +Now, [Composer][composer] will automatically download all required files, +and install them for you. -Requirements ------------- +## Requirements -You need at least PHP 5.3.3 and optionally have support for gzip compression +You need at least PHP 8.1, and optionally have support for gzip compression enabled. This package has no other external dependencies. -Basic usage ------------ +## Basic usage The usage of this library is very straightforward, each encoder encodes and decodes a URL string. -To encode a string for safe usage in URL call `encodeUri()` on the encoder object. - -To decode an encoded string, to the original value call `decodeUri()` on the encoder object. +To encode a string for safe usage in a URL call `encodeUri()` on the encoder. +To decode an encoded string, to the original value call `decodeUri()` on the encoder. -**Note:** Decoders will silently ignore invalid data, and return null instead. +**Note:** The `decode()` method will silently ignore invalid data, +and return null instead. ### Base64UriEncoder ```php - -require 'vendor/autoload.php'; - use Rollerworks\Component\UriEncoder\Encoder as UriEncoder; $stringEncode = 'This string is not safe, for direct usage & must encoded'; @@ -78,19 +66,16 @@ A decorator operates on top of the actual encoder. * `encodeUri()` modifies the value returned by the decorated encoder. * `decodeUri()` modifies the passed-in value before passing to the decorated encoder. -These decorators can not be used as a stand-alone! +These decorators cannot be used as a stand-alone. #### GZipCompressionDecorator The `GZipCompressionDecorator` (de)compresses URI data. -**Caution:** The GZipCompressionDecorator creates a non-safe binary result, +**Caution:** The `GZipCompressionDecorator` creates a non-safe binary result, make sure the original encoder supports this. ```php - -require 'vendor/autoload.php'; - use Rollerworks\Component\UriEncoder\Encoder as UriEncoder; $stringEncode = 'This string is not safe, for direct usage & must encoded'; @@ -126,8 +111,15 @@ For more information on SemVer, please visit . License ------- -The package is provided under the none-restrictive MIT license, -you are free to use it for any free or proprietary product/application, -without restrictions. +This library is released under the [MIT license](LICENSE). + +## Contributing + +This is an open source project. If you'd like to contribute, +please read the [Contributing Guidelines][contributing]. If you're submitting +a pull request, please follow the guidelines in the [Submitting a Patch][patches] section.ß -[LICENSE](LICENSE) +[composer]: https://getcomposer.org/doc/00-intro.md +[flex]: https://symfony.com/doc/current/setup/flex.html +[contributing]: https://contributing.rollerscapes.net/ +[patches]: https://contributing.rollerscapes.net/latest/patches.html diff --git a/composer.json b/composer.json index 54ef979..a8b73be 100644 --- a/composer.json +++ b/composer.json @@ -11,20 +11,28 @@ }, { "name": "Community contributions", - "homepage": "https://github.com/Rollerworks/uri-encoder/contributors" + "homepage": "https://github.com/rollerworks/uri-encoder/contributors" } ], "require": { - "php": "^7.1" + "php": "^8.1", + "symfony/polyfill-mbstring": "^1.28" }, "require-dev": { - "phpspec/phpspec": "^3.4.2" + "ext-zlib": "*", + "rollerscapes/standards": "^1.0", + "phpunit/phpunit": "^10.4" }, + "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Rollerworks\\Component\\UriEncoder\\": "src/" }, - "exclude-from-classmap": ["spec/"] + "exclude-from-classmap": ["tests/"] + }, + "autoload-dev": { + "Rollerworks\\Component\\UriEncoder\\Tests\\": "tests/" }, "extra": { "branch-alias": { diff --git a/phpspec.yml b/phpspec.yml deleted file mode 100644 index 07d8a32..0000000 --- a/phpspec.yml +++ /dev/null @@ -1,8 +0,0 @@ -formatter.name: pretty - -suites: - uri_encoder_suite: - namespace: Rollerworks\Component\UriEncoder - psr4_prefix: Rollerworks\Component\UriEncoder - src_path: src/ - spec_prefix: spec\ diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..0e56097 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,16 @@ +includes: + - vendor/rollerscapes/standards/phpstan.neon + #- phpstan-baseline.neon + +parameters: + #reportUnmatchedIgnoredErrors: false + + paths: + - ./src + - ./tests + excludePaths: + - var/ + - templates/ + - translations/ + + #ignoreErrors: diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..b040071 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + tests + + + + + + + + + + + src/ + + + vendor/ + tests/ + + + diff --git a/spec/Encoder/Base64UriEncoderSpec.php b/spec/Encoder/Base64UriEncoderSpec.php deleted file mode 100644 index 3e75485..0000000 --- a/spec/Encoder/Base64UriEncoderSpec.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace spec\Rollerworks\Component\UriEncoder\Encoder; - -use PhpSpec\ObjectBehavior; - -final class Base64UriEncoderSpec extends ObjectBehavior -{ - public function it_is_initializable() - { - $this->shouldHaveType('Rollerworks\Component\UriEncoder\Encoder\Base64UriEncoder'); - } - - public function its_an_encoder() - { - $this->shouldHaveType('Rollerworks\Component\UriEncoder\UriEncoderInterface'); - } - - public function it_encodes_a_uri() - { - $this->encodeUri('foo-bar-car')->shouldReturn('Zm9vLWJhci1jYXI'); - } - - public function it_decodes_an_encoded_uri() - { - $this->decodeUri('Zm9vLWJhci1jYXI')->shouldReturn('foo-bar-car'); - } - - public function it_returns_null_on_invalid_encoded_data() - { - $this->decodeUri('[whoops]')->shouldReturn(null); - } -} diff --git a/spec/Encoder/GZipCompressionDecoratorSpec.php b/spec/Encoder/GZipCompressionDecoratorSpec.php deleted file mode 100644 index aad94bc..0000000 --- a/spec/Encoder/GZipCompressionDecoratorSpec.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace spec\Rollerworks\Component\UriEncoder\Encoder; - -use PhpSpec\ObjectBehavior; -use Prophecy\Argument; -use Rollerworks\Component\UriEncoder\UriEncoderInterface; - -final class GZipCompressionDecoratorSpec extends ObjectBehavior -{ - public function let(UriEncoderInterface $encoder) - { - // Use a callback as we process binary data here - $encoder->encodeUri(Argument::any())->will(function ($data) { - return base64_encode($data[0]); - }); - - $encoder->decodeUri(Argument::any())->will(function ($data) { - return base64_decode($data[0], true); - }); - - $this->beConstructedWith($encoder); - } - - public function it_is_initializable() - { - $this->shouldHaveType('Rollerworks\Component\UriEncoder\Encoder\GZipCompressionDecorator'); - } - - public function its_an_encoder() - { - $this->shouldHaveType('Rollerworks\Component\UriEncoder\UriEncoderInterface'); - } - - public function it_encodes_a_uri() - { - $this->encodeUri('foo-bar-car')->shouldReturn('eJxLy8/XTUos0k1OLAIAGFEECg=='); - } - - public function it_decodes_an_encoded_uri() - { - $this->decodeUri('eJxLy8/XTUos0k1OLAIAGFEECg==')->shouldReturn('foo-bar-car'); - } - - public function it_returns_null_on_invalid_encoded_data() - { - $this->decodeUri('[whoops]')->shouldReturn(null); - } -} diff --git a/src/Encoder/Base64UriEncoder.php b/src/Encoder/Base64UriEncoder.php index d3e2b8a..b2b9104 100644 --- a/src/Encoder/Base64UriEncoder.php +++ b/src/Encoder/Base64UriEncoder.php @@ -19,27 +19,24 @@ * Base64UriEncoder encodes/decodes URI using URI-safe base64. * * Unsafe characters are converted and stripped. - * - * @author Sebastiaan Stok */ final class Base64UriEncoder implements UriEncoderInterface { - /** - * {@inheritdoc} - */ public function encodeUri(string $data): string { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } - /** - * {@inheritdoc} - */ public function decodeUri(string $data): ?string { try { - return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT), true) ?? null; - } catch (\Throwable $e) { + $decode = base64_decode( + str_pad(strtr($data, '-_', '+/'), mb_strlen($data, '8bit') % 4, '='), + true + ); + + return $decode === false ? null : $decode; + } catch (\Throwable) { return null; } } diff --git a/src/Encoder/GZipCompressionDecorator.php b/src/Encoder/GZipCompressionDecorator.php index 0d12849..5b92363 100644 --- a/src/Encoder/GZipCompressionDecorator.php +++ b/src/Encoder/GZipCompressionDecorator.php @@ -18,46 +18,29 @@ /** * GZipCompressionDecorator (de)compresses URI data and delegates * back to the original Encoder. - * - * @author Sebastiaan Stok */ final class GZipCompressionDecorator implements UriEncoderInterface { - /** - * @var UriEncoderInterface - */ - private $encoder; + public function __construct(private UriEncoderInterface $encoder) {} - /** - * @param UriEncoderInterface $encoder - */ - public function __construct(UriEncoderInterface $encoder) - { - $this->encoder = $encoder; - } - - /** - * {@inheritdoc} - */ public function encodeUri(string $data): string { - return $this->encoder->encodeUri(gzcompress($data)); + return $this->encoder->encodeUri((string) gzcompress($data)); } - /** - * {@inheritdoc} - */ public function decodeUri(string $data): ?string { - $data = $this->encoder->decodeUri($data); + $decoded = $this->encoder->decodeUri($data); - if (null === $data) { + if ($decoded === null) { return null; } try { - return gzuncompress($data) ?? null; - } catch (\Throwable $e) { + $compressed = gzuncompress($decoded); + + return $compressed === false ? null : $compressed; + } catch (\Throwable) { return null; } } diff --git a/src/UriEncoderInterface.php b/src/UriEncoderInterface.php index 60f954c..b28814e 100644 --- a/src/UriEncoderInterface.php +++ b/src/UriEncoderInterface.php @@ -15,26 +15,16 @@ /** * UriEncoderInterface encodes a string for URL usage. - * - * @author Sebastiaan Stok */ interface UriEncoderInterface { /** * Encodes the URI to a usable format. - * - * @param string $data - * - * @return string */ public function encodeUri(string $data): string; /** * Decodes the encoded URI back to the original format. - * - * @param string $data - * - * @return string|null */ public function decodeUri(string $data): ?string; } diff --git a/tests/Encoder/Base64UriEncoderTest.php b/tests/Encoder/Base64UriEncoderTest.php new file mode 100644 index 0000000..cf6fae8 --- /dev/null +++ b/tests/Encoder/Base64UriEncoderTest.php @@ -0,0 +1,50 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Rollerworks\Component\UriEncoder\Tests\Encoder; + +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Rollerworks\Component\UriEncoder\Encoder\Base64UriEncoder; + +final class Base64UriEncoderTest extends TestCase +{ + #[Test] + public function it_encodes_a_uri(): void + { + $encoder = new Base64UriEncoder(); + + self::assertSame('Zm9vLWJhci1jYXI', $encoder->encodeUri('foo-bar-car')); + self::assertSame('Zm9vL2Jhci9jYXI', $encoder->encodeUri('foo/bar/car')); + self::assertSame('Zm9vL2Jhcj9jYXI', $encoder->encodeUri('foo/bar?car')); + } + + #[Test] + public function it_decodes_an_encoded_uri(): void + { + $encoder = new Base64UriEncoder(); + + self::assertSame('foo-bar-car', $encoder->decodeUri('Zm9vLWJhci1jYXI')); + self::assertSame('foo/bar/car', $encoder->decodeUri('Zm9vL2Jhci9jYXI')); + self::assertSame('foo/bar?car', $encoder->decodeUri('Zm9vL2Jhcj9jYXI')); + } + + #[Test] + public function it_returns_null_on_invalid_encoded_data(): void + { + $encoder = new Base64UriEncoder(); + + self::assertNull($encoder->decodeUri('[whoops]')); + self::assertNull($encoder->decodeUri('AZZSDAFSF')); + } +} diff --git a/tests/Encoder/GZipCompressionDecoratorTest.php b/tests/Encoder/GZipCompressionDecoratorTest.php new file mode 100644 index 0000000..c0e8b67 --- /dev/null +++ b/tests/Encoder/GZipCompressionDecoratorTest.php @@ -0,0 +1,64 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Rollerworks\Component\UriEncoder\Tests\Encoder; + +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use Rollerworks\Component\UriEncoder\Encoder\GZipCompressionDecorator; +use Rollerworks\Component\UriEncoder\UriEncoderInterface; + +final class GZipCompressionDecoratorTest extends TestCase +{ + #[Test] + public function it_encodes_a_uri(): void + { + $compression = new GZipCompressionDecorator($this->getEncoder()); + + self::assertSame('eJxLy8/XTUos0k1OLAIAGFEECg==', $compression->encodeUri('foo-bar-car')); + } + + #[Test] + public function it_decodes_an_encoded_uri(): void + { + $compression = new GZipCompressionDecorator($this->getEncoder()); + + self::assertSame('foo-bar-car', $compression->decodeUri('eJxLy8/XTUos0k1OLAIAGFEECg==')); + } + + #[Test] + public function it_returns_null_on_invalid_encoded_data(): void + { + $compression = new GZipCompressionDecorator($this->getEncoder()); + + self::assertNull($compression->decodeUri('[whoops]')); + self::assertNull($compression->decodeUri('xknvkjsa')); + } + + private function getEncoder(): UriEncoderInterface + { + return new class() implements UriEncoderInterface { + public function encodeUri(string $data): string + { + return base64_encode($data); + } + + public function decodeUri(string $data): ?string + { + $decode = base64_decode($data, true); + + return $decode === false ? null : $decode; + } + }; + } +}