Skip to content

Commit

Permalink
Merge 1e92e0f into b867505
Browse files Browse the repository at this point in the history
  • Loading branch information
kelunik authored Apr 30, 2020
2 parents b867505 + 1e92e0f commit 061c110
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 0 deletions.
64 changes: 64 additions & 0 deletions lib/Base64DecodingInputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Amp\ByteStream;

use Amp\Failure;
use Amp\Promise;
use function Amp\call;

final class Base64DecodingInputStream implements InputStream
{
/** @var InputStream */
private $source;

/** @var string|null */
private $buffer = '';

public function __construct(InputStream $source)
{
$this->source = $source;
}

public function read(): Promise
{
if ($this->source === null) {
return new Failure(new StreamException('Failed to read stream chunk due to invalid base64 data'));
}

return call(function () {
$chunk = yield $this->source->read();
if ($chunk === null) {
if ($this->buffer === null) {
return null;
}

$chunk = \base64_decode($this->buffer, true);
if ($chunk === false) {
$this->source = null;
$this->buffer = null;

throw new StreamException('Failed to read stream chunk due to invalid base64 data');
}

$this->buffer = null;

return $chunk;
}

$this->buffer .= $chunk;

$length = \strlen($this->buffer);
$chunk = \base64_decode(\substr($this->buffer, 0, $length - $length % 4), true);
if ($chunk === false) {
$this->source = null;
$this->buffer = null;

throw new StreamException('Failed to read stream chunk due to invalid base64 data');
}

$this->buffer = \substr($this->buffer, $length - $length % 4);

return $chunk;
});
}
}
53 changes: 53 additions & 0 deletions lib/Base64DecodingOutputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Amp\ByteStream;

use Amp\Failure;
use Amp\Promise;

final class Base64DecodingOutputStream implements OutputStream
{
/** @var OutputStream */
private $destination;

/** @var string */
private $buffer = '';

/** @var int */
private $offset = 0;

public function __construct(OutputStream $destination)
{
$this->destination = $destination;
}

public function write(string $data): Promise
{
$this->buffer .= $data;

$length = \strlen($this->buffer);
$chunk = \base64_decode(\substr($this->buffer, 0, $length - $length % 4), true);
if ($chunk === false) {
return new Failure(new StreamException('Invalid base64 near offset ' . $this->offset));
}

$this->offset += $length - $length % 4;
$this->buffer = \substr($this->buffer, $length - $length % 4);

return $this->destination->write($chunk);
}

public function end(string $finalData = ""): Promise
{
$this->offset += \strlen($this->buffer);

$chunk = \base64_decode($this->buffer . $finalData, true);
if ($chunk === false) {
return new Failure(new StreamException('Invalid base64 near offset ' . $this->offset));
}

$this->buffer = '';

return $this->destination->end($chunk);
}
}
45 changes: 45 additions & 0 deletions lib/Base64EncodingInputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Amp\ByteStream;

use Amp\Promise;
use function Amp\call;

final class Base64EncodingInputStream implements InputStream
{
/** @var InputStream */
private $source;

/** @var string|null */
private $buffer = '';

public function __construct(InputStream $source)
{
$this->source = $source;
}

public function read(): Promise
{
return call(function () {
$chunk = yield $this->source->read();
if ($chunk === null) {
if ($this->buffer === null) {
return null;
}

$chunk = \base64_encode($this->buffer);
$this->buffer = null;

return $chunk;
}

$this->buffer .= $chunk;

$length = \strlen($this->buffer);
$chunk = \base64_encode(\substr($this->buffer, 0, $length - $length % 3));
$this->buffer = \substr($this->buffer, $length - $length % 3);

return $chunk;
});
}
}
38 changes: 38 additions & 0 deletions lib/Base64EncodingOutputStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Amp\ByteStream;

use Amp\Promise;

final class Base64EncodingOutputStream implements OutputStream
{
/** @var OutputStream */
private $destination;

/** @var string */
private $buffer = '';

public function __construct(OutputStream $destination)
{
$this->destination = $destination;
}

public function write(string $data): Promise
{
$this->buffer .= $data;

$length = \strlen($this->buffer);
$chunk = \base64_encode(\substr($this->buffer, 0, $length - $length % 3));
$this->buffer = \substr($this->buffer, $length - $length % 3);

return $this->destination->write($chunk);
}

public function end(string $finalData = ""): Promise
{
$chunk = \base64_encode($this->buffer . $finalData);
$this->buffer = '';

return $this->destination->end($chunk);
}
}
69 changes: 69 additions & 0 deletions test/Base64DecodingInputStreamTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Amp\ByteStream\Test;

use Amp\ByteStream\Base64DecodingInputStream;
use Amp\ByteStream\InputStream;
use Amp\ByteStream\IteratorStream;
use Amp\ByteStream\StreamException;
use Amp\Emitter;
use Amp\PHPUnit\AsyncTestCase;
use function Amp\ByteStream\buffer;

class Base64DecodingInputStreamTest extends AsyncTestCase
{
/** @var Emitter */
private $emitter;

/** @var InputStream */
private $stream;

public function testRead(): \Generator
{
$promise = buffer($this->stream);

$this->emitter->emit('Z');
$this->emitter->emit('m9vLmJhcg=');
$this->emitter->emit('=');
$this->emitter->complete();

$this->assertSame('foo.bar', yield $promise);
}

public function testInvalidDataMissingPadding(): \Generator
{
$promise = buffer($this->stream);

$this->emitter->emit('Z');
$this->emitter->emit('m9vLmJhcg=');
$this->emitter->emit(''); // missing =
$this->emitter->complete();

$this->expectException(StreamException::class);
$this->expectExceptionMessage('Failed to read stream chunk due to invalid base64 data');

$this->assertSame('foo.bar', yield $promise);
}

public function testInvalidDataChar(): \Generator
{
$promise = buffer($this->stream);

$this->emitter->emit('Z');
$this->emitter->emit('!');
$this->emitter->complete();

$this->expectException(StreamException::class);
$this->expectExceptionMessage('Failed to read stream chunk due to invalid base64 data');

$this->assertSame('foo.bar', yield $promise);
}

protected function setUp(): void
{
parent::setUp();

$this->emitter = new Emitter;
$this->stream = new Base64DecodingInputStream(new IteratorStream($this->emitter->iterate()));
}
}
61 changes: 61 additions & 0 deletions test/Base64DecodingOutputStreamTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Amp\ByteStream\Test;

use Amp\ByteStream\Base64DecodingOutputStream;
use Amp\ByteStream\OutputBuffer;
use Amp\ByteStream\StreamException;
use Amp\PHPUnit\AsyncTestCase;

class Base64DecodingOutputStreamTest extends AsyncTestCase
{
public function testWrite(): \Generator
{
$buffer = new OutputBuffer;
$stream = new Base64DecodingOutputStream($buffer);

yield $stream->write('Zm9');
yield $stream->write('');
yield $stream->write('vLmJhcg==');
yield $stream->end();

$this->assertSame('foo.bar', yield $buffer);
}

public function testEnd(): \Generator
{
$buffer = new OutputBuffer;
$stream = new Base64DecodingOutputStream($buffer);

yield $stream->write('Zm9');
yield $stream->write('');
yield $stream->end('vLmJhcg==');

$this->assertSame('foo.bar', yield $buffer);
}

public function testInvalidDataMissingPadding(): \Generator
{
$buffer = new OutputBuffer;
$stream = new Base64DecodingOutputStream($buffer);

yield $stream->write('Zm9');
yield $stream->write('');

$this->expectException(StreamException::class);
$this->expectExceptionMessage('Invalid base64 near offset 3');

yield $stream->end('vLmJhcg=');
}

public function testInvalidDataChar(): \Generator
{
$buffer = new OutputBuffer;
$stream = new Base64DecodingOutputStream($buffer);

$this->expectException(StreamException::class);
$this->expectExceptionMessage('Invalid base64 near offset 0');

yield $stream->write('Z!fsdf');
}
}
43 changes: 43 additions & 0 deletions test/Base64EncodingInputStreamTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Amp\ByteStream\Test;

use Amp\ByteStream\Base64EncodingInputStream;
use Amp\ByteStream\InputStream;
use Amp\ByteStream\IteratorStream;
use Amp\Emitter;
use Amp\PHPUnit\AsyncTestCase;
use function Amp\ByteStream\buffer;

class Base64EncodingInputStreamTest extends AsyncTestCase
{
/** @var Emitter */
private $emitter;

/** @var InputStream */
private $stream;

public function testRead(): \Generator
{
$promise = buffer($this->stream);

$this->emitter->emit('f');
$this->emitter->emit('o');
$this->emitter->emit('o');
$this->emitter->emit('.');
$this->emitter->emit('b');
$this->emitter->emit('a');
$this->emitter->emit('r');
$this->emitter->complete();

$this->assertSame('Zm9vLmJhcg==', yield $promise);
}

protected function setUp(): void
{
parent::setUp();

$this->emitter = new Emitter;
$this->stream = new Base64EncodingInputStream(new IteratorStream($this->emitter->iterate()));
}
}
Loading

0 comments on commit 061c110

Please sign in to comment.