From 48b6c210900ee9edfebe2be282ae93a5d1931255 Mon Sep 17 00:00:00 2001 From: codemasher Date: Sun, 28 Nov 2021 18:25:06 +0100 Subject: [PATCH] :bath: QRDataModeInterface: validate data on init, test cleanup --- src/Data/AlphaNum.php | 20 +------ src/Data/Byte.php | 4 +- src/Data/Kanji.php | 4 ++ src/Data/Number.php | 6 +- src/Data/QRData.php | 11 ++-- src/Data/QRDataModeAbstract.php | 7 +++ tests/Data/AlphaNumTest.php | 35 +----------- tests/Data/ByteTest.php | 26 +++------ tests/Data/DatainterfaceTestAbstract.php | 72 ++++++++++-------------- tests/Data/KanjiTest.php | 47 +--------------- tests/Data/NumberTest.php | 35 +----------- tests/QRCodeReaderTest.php | 7 +-- 12 files changed, 71 insertions(+), 203 deletions(-) diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index 2a9bc0182..efe6cfaaf 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -12,7 +12,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Common\{BitBuffer, Mode}; -use function array_flip, ceil, ord, sprintf, str_split; +use function array_flip, ceil, str_split; /** * Alphanumeric mode: 0 to 9, A to Z, space, $ % * + - . / : @@ -75,30 +75,16 @@ final class AlphaNum extends QRDataModeAbstract{ // encode 2 characters in 11 bits for($i = 0; $i + 1 < $len; $i += 2){ - $bitBuffer->put($this->getCharCode($this->data[$i]) * 45 + $this->getCharCode($this->data[$i + 1]), 11); + $bitBuffer->put(self::CHAR_TO_ORD[$this->data[$i]] * 45 + self::CHAR_TO_ORD[$this->data[$i + 1]], 11); } // encode a remaining character in 6 bits if($i < $len){ - $bitBuffer->put($this->getCharCode($this->data[$i]), 6); + $bitBuffer->put(self::CHAR_TO_ORD[$this->data[$i]], 6); } } - /** - * get the code for the given character - * - * @throws \chillerlan\QRCode\Data\QRCodeDataException on an illegal character occurence - */ - private function getCharCode(string $chr):int{ - - if(isset(self::CHAR_TO_ORD[$chr])){ - return self::CHAR_TO_ORD[$chr]; - } - - throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, ord($chr))); - } - /** * @inheritDoc * diff --git a/src/Data/Byte.php b/src/Data/Byte.php index d2df4f4ac..74d799975 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -12,7 +12,7 @@ namespace chillerlan\QRCode\Data; use chillerlan\QRCode\Common\{BitBuffer, Mode}; -use function ord; +use function chr, ord; /** * Byte mode, ISO-8859-1 or UTF-8 @@ -76,7 +76,7 @@ final class Byte extends QRDataModeAbstract{ $readBytes = ''; for($i = 0; $i < $length; $i++){ - $readBytes .= \chr($bitBuffer->read(8)); + $readBytes .= chr($bitBuffer->read(8)); } return $readBytes; diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index 4d6b2c1ec..bcd62655c 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -57,6 +57,10 @@ final class Kanji extends QRDataModeAbstract{ $i = 0; $len = strlen($string); + if($len < 2){ + return false; + } + while($i + 1 < $len){ $c = ((0xff & ord($string[$i])) << 8) | (0xff & ord($string[$i + 1])); diff --git a/src/Data/Number.php b/src/Data/Number.php index ad8c34b89..53917eea2 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -98,14 +98,12 @@ final class Number extends QRDataModeAbstract{ $num = 0; foreach(str_split($string) as $chr){ - $c = ord($chr); if(!isset(self::NUMBER_TO_ORD[$chr])){ - throw new QRCodeDataException(sprintf('illegal char: "%s" [%d]', $chr, $c)); + throw new QRCodeDataException(sprintf('illegal char: "%s"', $chr)); } - $c = $c - 48; // ord('0') - $num = $num * 10 + $c; + $num = $num * 10 + ord($chr) - 48; } return $num; diff --git a/src/Data/QRData.php b/src/Data/QRData.php index 9b28d0d8b..c4286d66b 100644 --- a/src/Data/QRData.php +++ b/src/Data/QRData.php @@ -193,19 +193,16 @@ final class QRData{ // The message bit stream shall then be extended to fill the data capacity of the symbol // corresponding to the Version and Error Correction Level, by the addition of the Pad // Codewords 11101100 and 00010001 alternately. + $alternate = false; + while(true){ if($this->bitBuffer->getLength() >= $MAX_BITS){ break; } - $this->bitBuffer->put(0b11101100, 8); - - if($this->bitBuffer->getLength() >= $MAX_BITS){ - break; - } - - $this->bitBuffer->put(0b00010001, 8); + $this->bitBuffer->put($alternate ? 0b00010001 : 0b11101100, 8); + $alternate = !$alternate; } } diff --git a/src/Data/QRDataModeAbstract.php b/src/Data/QRDataModeAbstract.php index 9f1211162..17d22bc16 100644 --- a/src/Data/QRDataModeAbstract.php +++ b/src/Data/QRDataModeAbstract.php @@ -26,8 +26,15 @@ abstract class QRDataModeAbstract implements QRDataModeInterface{ /** * QRDataModeAbstract constructor. + * + * @throws \chillerlan\QRCode\Data\QRCodeDataException */ public function __construct(string $data){ + + if(!$this::validateString($data)){ + throw new QRCodeDataException('invalid data'); + } + $this->data = $data; } diff --git a/tests/Data/AlphaNumTest.php b/tests/Data/AlphaNumTest.php index ae99a772e..c5a105911 100644 --- a/tests/Data/AlphaNumTest.php +++ b/tests/Data/AlphaNumTest.php @@ -10,43 +10,14 @@ namespace chillerlan\QRCodeTest\Data; -use chillerlan\QRCode\Data\{AlphaNum, QRCodeDataException}; +use chillerlan\QRCode\Data\AlphaNum; /** * Tests the AlphaNum class */ final class AlphaNumTest extends DatainterfaceTestAbstract{ - /** @internal */ - protected array $testdata = [AlphaNum::class, '0 $%*+-./:']; - - /** @internal */ - protected array $expected = [ - 32, 80, 36, 212, 252, 15, 175, 251, - 176, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 112, 43, 9, 248, 200, 194, 75, 25, - 205, 173, 154, 68, 191, 16, 128, - 92, 112, 20, 198, 27 - ]; - - /** - * Tests if an exception is thrown when an invalid character is encountered - */ - public function testGetCharCodeException():void{ - $this->expectException(QRCodeDataException::class); - $this->expectExceptionMessage('illegal char: "#" [35]'); - - $this->testdata = [AlphaNum::class, '#']; - - $this->setTestData(); - } + protected string $FQN = AlphaNum::class; + protected string $testdata = '0 $%*+-./:'; } diff --git a/tests/Data/ByteTest.php b/tests/Data/ByteTest.php index dce159b66..90d80699c 100644 --- a/tests/Data/ByteTest.php +++ b/tests/Data/ByteTest.php @@ -17,24 +17,14 @@ use chillerlan\QRCode\Data\Byte; */ final class ByteTest extends DatainterfaceTestAbstract{ - /** @internal */ - protected array $testdata = [Byte::class, '[¯\_(ツ)_/¯]']; + protected string $FQN = Byte::class; + protected string $testdata = '[¯\_(ツ)_/¯]'; - /** @internal */ - protected array $expected = [ - 64, 245, 188, 42, 245, 197, 242, 142, - 56, 56, 66, 149, 242, 252, 42, 245, - 208, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 79, 89, 226, 48, 209, 89, 151, 1, - 12, 73, 42, 163, 11, 34, 255, 205, - 21, 47, 250, 101 - ]; + /** + * @inheritDoc + */ + public function testInvalidDataException():void{ + $this->markTestSkipped('N/A'); + } } diff --git a/tests/Data/DatainterfaceTestAbstract.php b/tests/Data/DatainterfaceTestAbstract.php index a747cba61..392cf5b23 100644 --- a/tests/Data/DatainterfaceTestAbstract.php +++ b/tests/Data/DatainterfaceTestAbstract.php @@ -24,28 +24,16 @@ use function str_repeat; */ abstract class DatainterfaceTestAbstract extends TestCase{ - /** @internal */ protected ReflectionClass $reflection; - /** @internal */ - protected QRData $dataInterface; - /** @internal */ - protected array $testdata; - /** @internal */ - protected array $expected; + protected QRData $dataInterface; + protected string $FQN; + protected string $testdata; - /** - * @internal - */ protected function setUp():void{ - $this->dataInterface = new QRData(new QROptions(['version' => 4]), []); + $this->dataInterface = new QRData(new QROptions(['version' => 4])); $this->reflection = new ReflectionClass($this->dataInterface); } - protected function setTestData():void{ - [$class, $data] = $this->testdata; - $this->dataInterface->setData([new $class($data)]); - } - /** * Verifies the data interface instance */ @@ -53,38 +41,21 @@ abstract class DatainterfaceTestAbstract extends TestCase{ $this::assertInstanceOf(QRData::class, $this->dataInterface); } - /** - * Tests ecc masking and verifies against a sample - */ -/* public function testMaskEcc():void{ - $this->dataInterface->setData([$this->testdata]); - - $maskECC = $this->reflection->getMethod('maskECC'); - $maskECC->setAccessible(true); - - $bitBuffer = $this->reflection->getProperty('bitBuffer'); - $bitBuffer->setAccessible(true); - $bb = $bitBuffer->getValue($this->dataInterface); - - $this::assertSame($this->expected, $maskECC->invokeArgs($this->dataInterface, [$bb->getBuffer()])); - }*/ - /** * @see testInitMatrix() - * @internal * @return int[][] */ - public function MaskPatternProvider():array{ + public function maskPatternProvider():array{ return [[0], [1], [2], [3], [4], [5], [6], [7]]; } /** * Tests initializing the data matrix * - * @dataProvider MaskPatternProvider + * @dataProvider maskPatternProvider */ public function testInitMatrix(int $maskPattern):void{ - $this->setTestData(); + $this->dataInterface->setData([new $this->FQN($this->testdata)]); $matrix = $this->dataInterface->writeMatrix(new MaskPattern($maskPattern)); @@ -96,7 +67,7 @@ abstract class DatainterfaceTestAbstract extends TestCase{ * Tests getting the minimum QR version for the given data */ public function testGetMinimumVersion():void{ - $this->setTestData(); + $this->dataInterface->setData([new $this->FQN($this->testdata)]); $getMinimumVersion = $this->reflection->getMethod('getMinimumVersion'); $getMinimumVersion->setAccessible(true); @@ -110,11 +81,10 @@ abstract class DatainterfaceTestAbstract extends TestCase{ public function testGetMinimumVersionException():void{ $this->expectException(QRCodeDataException::class); $this->expectExceptionMessage('data exceeds'); - [$class, $data] = $this->testdata; $this->dataInterface = new QRData( new QROptions(['version' => QRCode::VERSION_AUTO]), - [new $class(str_repeat($data, 1337))] + [new $this->FQN(str_repeat($this->testdata, 1337))] ); } @@ -124,10 +94,28 @@ abstract class DatainterfaceTestAbstract extends TestCase{ public function testCodeLengthOverflowException():void{ $this->expectException(QRCodeDataException::class); $this->expectExceptionMessage('code length overflow'); - [$class, $data] = $this->testdata; - $this->testdata = [$class, str_repeat($data, 1337)]; - $this->setTestData(); + $this->dataInterface->setData([new $this->FQN(str_repeat($this->testdata, 1337))]); + } + + /** + * Tests if an exception is thrown when an invalid character is encountered + */ + public function testInvalidDataException():void{ + $this->expectException(QRCodeDataException::class); + $this->expectExceptionMessage('invalid data'); + + $this->dataInterface->setData([new $this->FQN('##')]); + } + + /** + * Tests if an exception is thrown if the given string is empty + */ + public function testInvalidDataOnEmptyException():void{ + $this->expectException(QRCodeDataException::class); + $this->expectExceptionMessage('invalid data'); + + $this->dataInterface->setData([new $this->FQN('')]); } } diff --git a/tests/Data/KanjiTest.php b/tests/Data/KanjiTest.php index aae2b9b25..e277d83a3 100644 --- a/tests/Data/KanjiTest.php +++ b/tests/Data/KanjiTest.php @@ -10,55 +10,14 @@ namespace chillerlan\QRCodeTest\Data; -use chillerlan\QRCode\Data\{Kanji, QRCodeDataException}; +use chillerlan\QRCode\Data\Kanji; /** * Tests the Kanji class */ final class KanjiTest extends DatainterfaceTestAbstract{ - /** @internal */ - protected array $testdata = [Kanji::class, '茗荷茗荷茗荷茗荷茗荷']; - - /** @internal */ - protected array $expected = [ - 128, 173, 85, 26, 95, 85, 70, 151, - 213, 81, 165, 245, 84, 105, 125, 85, - 26, 92, 0, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 195, 11, 221, 91, 141, 220, 163, 46, - 165, 37, 163, 176, 79, 0, 64, 68, - 96, 113, 54, 191 - ]; - - /** - * Tests if an exception is thrown when an invalid character is encountered - */ - public function testIllegalCharException1():void{ - $this->expectException(QRCodeDataException::class); - $this->expectExceptionMessage('illegal char at 1 [16191]'); - - $this->testdata = [Kanji::class, 'ÃÃ']; - - $this->setTestData(); - } - - /** - * Tests if an exception is thrown when an invalid character is encountered - */ - public function testIllegalCharException2():void{ - $this->expectException(QRCodeDataException::class); - $this->expectExceptionMessage('illegal char at 1'); - - $this->testdata = [Kanji::class, 'Ã']; - - $this->setTestData(); - } + protected string $FQN = Kanji::class; + protected string $testdata = '茗荷茗荷茗荷茗荷茗荷'; } diff --git a/tests/Data/NumberTest.php b/tests/Data/NumberTest.php index 6400c5340..dcc40b093 100644 --- a/tests/Data/NumberTest.php +++ b/tests/Data/NumberTest.php @@ -10,43 +10,14 @@ namespace chillerlan\QRCodeTest\Data; -use chillerlan\QRCode\Data\{Number, QRCodeDataException}; +use chillerlan\QRCode\Data\Number; /** * Tests the Number class */ final class NumberTest extends DatainterfaceTestAbstract{ - /** @internal */ - protected array $testdata = [Number::class, '0123456789']; - - /** @internal */ - protected array $expected = [ - 16, 40, 12, 86, 106, 105, 0, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 17, 236, 17, 236, 17, 236, 17, 236, - 201, 141, 102, 116, 238, 162, 239, 230, - 222, 37, 79, 192, 42, 109, 188, 72, - 89, 63, 168, 151 - ]; - - /** - * Tests if an exception is thrown when an invalid character is encountered - */ - public function testGetCharCodeException():void{ - $this->expectException(QRCodeDataException::class); - $this->expectExceptionMessage('illegal char: "#" [35]'); - - $this->testdata = [Number::class, '#']; - - $this->setTestData(); - } + protected string $FQN = Number::class; + protected string $testdata = '0123456789'; } diff --git a/tests/QRCodeReaderTest.php b/tests/QRCodeReaderTest.php index 11d2d34a4..7bb22d220 100644 --- a/tests/QRCodeReaderTest.php +++ b/tests/QRCodeReaderTest.php @@ -106,12 +106,9 @@ class QRCodeReaderTest extends TestCase{ foreach([EccLevel::L, EccLevel::M, EccLevel::Q, EccLevel::H] as $ecc){ $eccLevel = new EccLevel($ecc); + $expected = substr($str, 0, $version->getMaxLengthForMode(Mode::BYTE, $eccLevel) ?? ''); - yield 'version: '.$version.$eccLevel => [ - $version, - $eccLevel, - substr($str, 0, $version->getMaxLengthForMode(Mode::BYTE, $eccLevel) ?? '') - ]; + yield 'version: '.$version.$eccLevel => [$version, $eccLevel, $expected]; } }