mirror of
https://github.com/danog/endtoend-test-psl.git
synced 2024-11-30 04:39:48 +01:00
[Encoding] introduce encoding component
This commit is contained in:
parent
883141b2a4
commit
fa55601439
33
src/Psl/Encoding/Base64/decode.php
Normal file
33
src/Psl/Encoding/Base64/decode.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Encoding\Base64;
|
||||||
|
|
||||||
|
use Psl\Encoding\Exception;
|
||||||
|
|
||||||
|
use function base64_decode;
|
||||||
|
use function preg_match;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a base64-encoded string into raw binary.
|
||||||
|
*
|
||||||
|
* Base64 character set:
|
||||||
|
* [A-Z] [a-z] [0-9] + /
|
||||||
|
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
|
||||||
|
*
|
||||||
|
* @psalm-pure
|
||||||
|
*
|
||||||
|
* @throws Exception\RangeException If the encoded string contains characters outside
|
||||||
|
* the base64 characters range.
|
||||||
|
*/
|
||||||
|
function decode(string $base64): string
|
||||||
|
{
|
||||||
|
if (!preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $base64)) {
|
||||||
|
throw new Exception\RangeException(
|
||||||
|
'The given base64 string contains characters outside the base64 range.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) @base64_decode($base64, true);
|
||||||
|
}
|
25
src/Psl/Encoding/Base64/encode.php
Normal file
25
src/Psl/Encoding/Base64/encode.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Encoding\Base64;
|
||||||
|
|
||||||
|
use Psl\Str;
|
||||||
|
|
||||||
|
use function base64_encode;
|
||||||
|
use function pack;
|
||||||
|
use function unpack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary string into a base64-encoded string.
|
||||||
|
*
|
||||||
|
* Base64 character set:
|
||||||
|
* [A-Z] [a-z] [0-9] + /
|
||||||
|
* 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
|
||||||
|
*
|
||||||
|
* @psalm-pure
|
||||||
|
*/
|
||||||
|
function encode(string $binary): string
|
||||||
|
{
|
||||||
|
return base64_encode($binary);
|
||||||
|
}
|
11
src/Psl/Encoding/Exception/ExceptionInterface.php
Normal file
11
src/Psl/Encoding/Exception/ExceptionInterface.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Encoding\Exception;
|
||||||
|
|
||||||
|
use Psl\Exception;
|
||||||
|
|
||||||
|
interface ExceptionInterface extends Exception\ExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
9
src/Psl/Encoding/Exception/IncorrectPaddingException.php
Normal file
9
src/Psl/Encoding/Exception/IncorrectPaddingException.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psl\Encoding\Exception;
|
||||||
|
|
||||||
|
use Psl\Exception;
|
||||||
|
|
||||||
|
final class IncorrectPaddingException extends Exception\InvalidArgumentException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
9
src/Psl/Encoding/Exception/RangeException.php
Normal file
9
src/Psl/Encoding/Exception/RangeException.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Encoding\Exception;
|
||||||
|
|
||||||
|
final class RangeException extends \RangeException implements ExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
39
src/Psl/Encoding/Hex/decode.php
Normal file
39
src/Psl/Encoding/Hex/decode.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Encoding\Hex;
|
||||||
|
|
||||||
|
use Psl\Encoding\Exception;
|
||||||
|
use Psl\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a hexadecimal string into a binary string.
|
||||||
|
*
|
||||||
|
* Hex ( Base16 ) character set:
|
||||||
|
* [0-9] [a-f] [A-F]
|
||||||
|
* 0x30-0x39, 0x61-0x66, 0x41-0x46
|
||||||
|
*
|
||||||
|
* @psalm-pure
|
||||||
|
*
|
||||||
|
* @throws Exception\RangeException If the hexadecimal string contains characters outside the base16 range,
|
||||||
|
* or an odd number of characters.
|
||||||
|
*/
|
||||||
|
function decode(string $hexadecimal): string
|
||||||
|
{
|
||||||
|
if (!ctype_xdigit($hexadecimal)) {
|
||||||
|
throw new Exception\RangeException(
|
||||||
|
'The given hexadecimal string contains characters outside the base16 range.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @psalm-suppress MissingThrowsDocblock */
|
||||||
|
$hex_len = Str\length($hexadecimal, '8bit');
|
||||||
|
if (($hex_len & 1) !== 0) {
|
||||||
|
throw new Exception\RangeException(
|
||||||
|
'Expected an even number of hexadecimal characters.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex2bin($hexadecimal);
|
||||||
|
}
|
19
src/Psl/Encoding/Hex/encode.php
Normal file
19
src/Psl/Encoding/Hex/encode.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Encoding\Hex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a binary string into a hexadecimal string.
|
||||||
|
*
|
||||||
|
* Hex ( Base16 ) character set:
|
||||||
|
* [0-9] [a-f]
|
||||||
|
* 0x30-0x39, 0x61-0x66
|
||||||
|
*
|
||||||
|
* @psalm-pure
|
||||||
|
*/
|
||||||
|
function encode(string $binary): string
|
||||||
|
{
|
||||||
|
return bin2hex($binary);
|
||||||
|
}
|
@ -323,6 +323,10 @@ final class Loader
|
|||||||
'Psl\Password\hash',
|
'Psl\Password\hash',
|
||||||
'Psl\Password\needs_rehash',
|
'Psl\Password\needs_rehash',
|
||||||
'Psl\Password\verify',
|
'Psl\Password\verify',
|
||||||
|
'Psl\Encoding\Base64\encode',
|
||||||
|
'Psl\Encoding\Base64\decode',
|
||||||
|
'Psl\Encoding\Hex\encode',
|
||||||
|
'Psl\Encoding\Hex\decode',
|
||||||
];
|
];
|
||||||
|
|
||||||
public const INTERFACES = [
|
public const INTERFACES = [
|
||||||
@ -340,6 +344,7 @@ final class Loader
|
|||||||
'Psl\Observer\SubjectInterface',
|
'Psl\Observer\SubjectInterface',
|
||||||
'Psl\Observer\ObserverInterface',
|
'Psl\Observer\ObserverInterface',
|
||||||
'Psl\Result\ResultInterface',
|
'Psl\Result\ResultInterface',
|
||||||
|
'Psl\Encoding\Exception\ExceptionInterface',
|
||||||
];
|
];
|
||||||
|
|
||||||
public const TRAITS = [
|
public const TRAITS = [
|
||||||
@ -379,6 +384,8 @@ final class Loader
|
|||||||
'Psl\Type\Type',
|
'Psl\Type\Type',
|
||||||
'Psl\Json\Exception\DecodeException',
|
'Psl\Json\Exception\DecodeException',
|
||||||
'Psl\Json\Exception\EncodeException',
|
'Psl\Json\Exception\EncodeException',
|
||||||
|
'Psl\Encoding\Exception\IncorrectPaddingException',
|
||||||
|
'Psl\Encoding\Exception\RangeException',
|
||||||
];
|
];
|
||||||
|
|
||||||
private const TYPE_CONSTANTS = 1;
|
private const TYPE_CONSTANTS = 1;
|
||||||
|
@ -13,6 +13,8 @@ use Psl\Iter;
|
|||||||
* @psalm-param (callable(): \Generator<Tk, Tv, mixed, mixed>) $fun
|
* @psalm-param (callable(): \Generator<Tk, Tv, mixed, mixed>) $fun
|
||||||
*
|
*
|
||||||
* @psalm-return Iter\Iterator<Tk, Tv>
|
* @psalm-return Iter\Iterator<Tk, Tv>
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
function lazy_iterator(callable $fun): Iter\Iterator
|
function lazy_iterator(callable $fun): Iter\Iterator
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,8 @@ use Psl;
|
|||||||
* @codeCoverageIgnore
|
* @codeCoverageIgnore
|
||||||
*
|
*
|
||||||
* @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds.
|
* @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
function validate_offset(int $offset, int $length): int
|
function validate_offset(int $offset, int $length): int
|
||||||
{
|
{
|
||||||
|
@ -13,6 +13,8 @@ use Psl;
|
|||||||
* @psalm-pure
|
* @psalm-pure
|
||||||
*
|
*
|
||||||
* @throws Psl\Exception\InvariantViolationException If $offset is out-of-bounds.
|
* @throws Psl\Exception\InvariantViolationException If $offset is out-of-bounds.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
function validate_offset_lower_bound(int $offset, int $length): int
|
function validate_offset_lower_bound(int $offset, int $length): int
|
||||||
{
|
{
|
||||||
|
36
tests/Psl/Encoding/Base64Test.php
Normal file
36
tests/Psl/Encoding/Base64Test.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Tests\Encoding;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psl\Encoding\Base64;
|
||||||
|
use Psl\Encoding\Exception;
|
||||||
|
use Psl\SecureRandom;
|
||||||
|
|
||||||
|
final class Base64Test extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideRandomBytes
|
||||||
|
*/
|
||||||
|
public function testEncodeAndDecode(string $random): void
|
||||||
|
{
|
||||||
|
$encoded = Base64\encode($random);
|
||||||
|
self::assertSame($random, Base64\decode($encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecodeThrowsForCharactersOutsideTheBase64Range(): void
|
||||||
|
{
|
||||||
|
$this->expectException(Exception\RangeException::class);
|
||||||
|
|
||||||
|
Base64\decode('@~==');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideRandomBytes(): iterable
|
||||||
|
{
|
||||||
|
for ($i = 1; $i < 128; ++$i) {
|
||||||
|
yield [SecureRandom\bytes($i)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
tests/Psl/Encoding/HexTest.php
Normal file
49
tests/Psl/Encoding/HexTest.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Psl\Tests\Encoding;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psl\Encoding\Hex;
|
||||||
|
use Psl\Encoding\Exception;
|
||||||
|
use Psl\SecureRandom;
|
||||||
|
use Psl\Str;
|
||||||
|
|
||||||
|
use function bin2hex;
|
||||||
|
|
||||||
|
final class HexTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider provideRandomBytes
|
||||||
|
*/
|
||||||
|
public function testRandom(string $random): void
|
||||||
|
{
|
||||||
|
$enc = Hex\encode($random);
|
||||||
|
self::assertSame($random, Hex\decode($enc));
|
||||||
|
self::assertSame(bin2hex($random), $enc);
|
||||||
|
$enc = Hex\encode($random);
|
||||||
|
self::assertSame($random, Hex\decode($enc));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecodeThrowsForCharactersOutsideTheHexRange(): void
|
||||||
|
{
|
||||||
|
$this->expectException(Exception\RangeException::class);
|
||||||
|
|
||||||
|
Hex\decode('gf');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDecodeThrowsForAnOddNumberOfCharacters(): void
|
||||||
|
{
|
||||||
|
$this->expectException(Exception\RangeException::class);
|
||||||
|
|
||||||
|
Hex\decode('f');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideRandomBytes(): iterable
|
||||||
|
{
|
||||||
|
for ($i = 1; $i < 128; ++$i) {
|
||||||
|
yield [SecureRandom\bytes($i)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user