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

Add factory methods #1

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/test export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml export-ignore
12 changes: 5 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ matrix:
fast_finish: true
include:
- php: "7.0"
os: linux
env: CHECK_MBSTRING=1
- php: "7.1"
os: linux
env: CHECK_MBSTRING=1
- php: "7.2"
os: linux
Expand All @@ -34,10 +32,10 @@ matrix:
- php: "master"

install:
- composer self-update
- composer update
- composer self-update
- composer update

script:
- vendor/bin/phpunit
- if [[ $CHECK_MBSTRING -eq 1 ]]; then php -dmbstring.func_overload=7 vendor/bin/phpunit; fi
- vendor/bin/psalm
- vendor/bin/phpunit
- if [[ $CHECK_MBSTRING -eq 1 ]]; then php -dmbstring.func_overload=7 vendor/bin/phpunit; fi
- vendor/bin/psalm
8 changes: 7 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.0/phpunit.xsd"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="./vendor/autoload.php"
Expand All @@ -11,4 +11,10 @@
<directory suffix="Test.php">./test</directory>
</testsuite>
</testsuites>

<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>
65 changes: 36 additions & 29 deletions src/HiddenString.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,24 @@ final class HiddenString
/**
* @var string
*/
protected $internalStringValue = '';
private $internalStringValue;

/**
* Disallow the contents from being accessed via __toString()?
*
* @var bool
*/
protected $disallowInline = false;
private $disallowInline;

/**
* Disallow the contents from being accessed via __sleep()?
*
* @var bool
*/
protected $disallowSerialization = false;
private $disallowSerialization;

/**
* HiddenString constructor.
* @param string $value
* @param bool $disallowInline
* @param bool $disallowSerialization
*
* @throws \TypeError
* @deprecated Please use one of the static factory methods.
*/
public function __construct(
string $value,
Expand All @@ -59,11 +54,38 @@ public function __construct(
}

/**
* @param HiddenString $other
* @return bool
* @throws \TypeError
* Create an instance that does not allow inlining and serialization.
*/
public function equals(HiddenString $other)
public static function create(string $value): self
{
return new self($value, true, true);
}

/**
* Create an instance that supports casting to string.
*/
public static function createInlineable(string $value): self
{
return new self($value, false, true);
}

/**
* Create an instance that can be serialized.
*/
public static function createSerializable(string $value): self
{
return new self($value, true, false);
}

/**
* Create an instance that can be cast to string and be serialized.
*/
public static function createOpen(string $value): self
{
return new self($value, false, false);
}

public function equals(HiddenString $other): bool
{
return \hash_equals(
$this->getString(),
Expand All @@ -73,10 +95,8 @@ public function equals(HiddenString $other)

/**
* Hide its internal state from var_dump()
*
* @return array
*/
public function __debugInfo()
public function __debugInfo(): array
{
return [
'internalStringValue' =>
Expand Down Expand Up @@ -116,9 +136,6 @@ public function __destruct()

/**
* Explicit invocation -- get the raw string value
*
* @return string
* @throws \TypeError
*/
public function getString(): string
{
Expand All @@ -128,9 +145,6 @@ public function getString(): string
/**
* Returns a copy of the string's internal value, which should be zeroed.
* Optionally, it can return an empty string.
*
* @return string
* @throws \TypeError
*/
public function __toString(): string
{
Expand All @@ -140,9 +154,6 @@ public function __toString(): string
return '';
}

/**
* @return array
*/
public function __sleep(): array
{
if (!$this->disallowSerialization) {
Expand All @@ -158,10 +169,6 @@ public function __sleep(): array
/**
* PHP 7 uses interned strings. We don't want altering this one to alter
* the original string.
*
* @param string $string
* @return string
* @throws \TypeError
*/
public static function safeStrcpy(string $string): string
{
Expand Down
114 changes: 73 additions & 41 deletions test/HiddenStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@

final class HiddenStringTest extends TestCase
{
/**
* @throws Exception
* @throws TypeError
*/
public function testEquals()
{
$A = new HiddenString(Base64UrlSafe::encode(random_bytes(32)));
$B = new HiddenString(Base64UrlSafe::encode(random_bytes(32)));
$C = new HiddenString($A->getString());
$D = new HiddenString($B->getString());
$A = HiddenString::create(Base64UrlSafe::encode(random_bytes(32)));
$B = HiddenString::create(Base64UrlSafe::encode(random_bytes(32)));
$C = HiddenString::create($A->getString());
$D = HiddenString::create($B->getString());

$this->assertFalse($A->equals($B));
$this->assertTrue($A->equals($C));
Expand All @@ -33,43 +29,79 @@ public function testEquals()
}

/**
* @throws Exception
* @throws TypeError
* @dataProvider dpOptions
*/
public function testRandomString()
public function testRandomString(bool $disallowInline, bool $disallowSerialization)
{
$str = Base64UrlSafe::encode(random_bytes(32));

$sets = [
[true, true],
[true, false],
[false, true],
[false, false]
];
foreach ($sets as $set) {
$hidden = new HiddenString($str, $set[0], $set[1]);

ob_start();
var_dump($hidden);
$dump = ob_get_clean();
$this->assertFalse(strpos($dump, $str));

$print = \print_r($hidden, true);
$this->assertFalse(strpos($print, $str));

$cast = (string) $hidden;
if ($set[0]) {
$this->assertFalse(strpos($cast, $str));
} else {
$this->assertNotFalse(strpos($cast, $str));
}

$serial = serialize($hidden);
if ($set[1]) {
$this->assertFalse(strpos($serial, $str));
} else {
$this->assertNotFalse(strpos($serial, $str));
}
$hidden = new HiddenString($str, $disallowInline, $disallowSerialization);

$this->assertStringNotPresentWhenDumped($hidden, $str);

$cast = (string)$hidden;
if ($disallowInline) {
$this->assertFalse(strpos($cast, $str));
} else {
$this->assertNotFalse(strpos($cast, $str));
}

$serial = serialize($hidden);
if ($disallowSerialization) {
$this->assertFalse(strpos($serial, $str));
} else {
$this->assertNotFalse(strpos($serial, $str));
}
}

public function dpOptions(): array
{
return [
'disallow both' => [true, true],
'serializable' => [true, false],
'inlineable' => [false, true],
'allow both' => [false, false],
];
}

public function testCreateInlineable()
{
$value = Base64UrlSafe::encode(random_bytes(32));
$hidden = HiddenString::createInlineable($value);

$this->assertStringNotPresentWhenDumped($hidden, $value);
$this->assertNotFalse(strpos((string)$hidden, $value));
$this->assertFalse(strpos(serialize($hidden), $value));
}

public function testCreateSerializable()
{
$value = Base64UrlSafe::encode(random_bytes(32));
$hidden = HiddenString::createSerializable($value);

$this->assertStringNotPresentWhenDumped($hidden, $value);
$this->assertFalse(strpos((string)$hidden, $value));
$this->assertNotFalse(strpos(serialize($hidden), $value));
}

public function testCreateOpen()
{
$value = Base64UrlSafe::encode(random_bytes(32));
$hidden = HiddenString::createOpen($value);

$this->assertStringNotPresentWhenDumped($hidden, $value);
$this->assertNotFalse(strpos((string)$hidden, $value));
$this->assertNotFalse(strpos(serialize($hidden), $value));
}

private function assertStringNotPresentWhenDumped(HiddenString $hidden, string $string)
{
ob_start();
var_dump($hidden);
$dump = ob_get_clean();
$this->assertFalse(strpos($dump, $string));

$print = \print_r($hidden, true);
$this->assertFalse(strpos($print, $string));
}
}