├── .gitignore ├── .travis.yml ├── tests ├── bootstrap.php └── Dflydev │ └── Base32 │ └── Crockford │ └── CrockfordTest.php ├── phpunit.xml.dist ├── composer.json ├── LICENSE ├── README.md └── src └── Dflydev └── Base32 └── Crockford └── Crockford.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3.3 5 | - 5.3 6 | - 5.4 7 | 8 | before_script: 9 | - wget -nc http://getcomposer.org/composer.phar 10 | - php composer.phar install 11 | 12 | script: phpunit --coverage-text --verbose 13 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('Dflydev\\Base32\\Crockford', 'tests'); 14 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests/Dflydev/Base32/Crockford 6 | 7 | 8 | 9 | 10 | 11 | ./src/Dflydev/Base32/Crockford/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflydev/base32-crockford", 3 | "type": "library", 4 | "description": "Encode/decode numbers using Douglas Crockford's Base32 Encoding", 5 | "homepage": "https://github.com/dflydev/dflydev-base32-crockford", 6 | "keywords": ["encode", "encoder", "decode", "decoder", "base32", "crockford"], 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Dragonfly Development Inc.", 11 | "email": "info@dflydev.com", 12 | "homepage": "http://dflydev.com" 13 | }, 14 | { 15 | "name": "Beau Simensen", 16 | "email": "beau@dflydev.com", 17 | "homepage": "http://beausimensen.com" 18 | } 19 | ], 20 | "require": { 21 | "php": ">=5.3.2" 22 | }, 23 | "autoload": { 24 | "psr-0": { 25 | "Dflydev\\Base32\\Crockford": "src" 26 | } 27 | }, 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "1.0-dev" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dragonfly Development Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Base32 Crockford Encoder and Decoder 2 | ==================================== 3 | 4 | A [Base32 Crockford](http://www.crockford.com/wrmg/base32.html) implementation 5 | for PHP. 6 | 7 | 8 | Example 9 | ------- 10 | 11 | use Dflydev\Base32\Crockford\Crockford; 12 | 13 | $encodedValue = Crockford::encode('519571'); // FVCK 14 | $decodedValue = Crockford::decode('FVCK'); // 519571 15 | 16 | $encodedValue = Crockford::encodeWithChecksum('519571'); // FVCKH 17 | $decodedValue = Crockford::decodeWithChecksum('FVCKH'); // 519571 18 | 19 | By default, decoding will be lenient on the input values. This will 20 | allow for passing in the following: 21 | 22 | $decodedValue = Crockford::decode('F-VCk'); // treated as: FVCK 23 | $decodedValue = Crockford::decode('hEl1O'); // treated as: HE110 24 | 25 | See [the spec](http://www.crockford.com/wrmg/base32.html) for the 26 | translation rules. 27 | 28 | Decoding can be made strict by passing an optional second argument 29 | to the decode methods. 30 | 31 | Crockford::decode('F-VCk', Crockford::NORMALIZE_ERRMODE_EXCEPTION); 32 | Crockford::decode('hEl1O', Crockford::NORMALIZE_ERRMODE_EXCEPTION); 33 | 34 | 35 | Requirements 36 | ------------ 37 | 38 | * PHP 5.3+ 39 | 40 | 41 | License 42 | ------- 43 | 44 | MIT, see LICENSE. 45 | 46 | 47 | Community 48 | --------- 49 | 50 | If you have questions or want to help out, join us in the 51 | [#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net. 52 | 53 | 54 | Not Invented Here 55 | ----------------- 56 | 57 | This is a port of [Encode::Base32::Crockford](https://github.com/gbarr/Encode-Base32-Crockford). 58 | -------------------------------------------------------------------------------- /tests/Dflydev/Base32/Crockford/CrockfordTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Requires 64bit system (int size is '.PHP_INT_SIZE.')'); 23 | 24 | return; 25 | } 26 | 27 | $this->assertEquals($encodedValue, Crockford::encode($decodedValue)); 28 | $this->assertEquals($encodedValueWithChecksum, Crockford::encodeWithChecksum($decodedValue)); 29 | } 30 | 31 | public function testEncodeThrowsException() 32 | { 33 | try { 34 | Crockford::encode("hello world"); 35 | 36 | $this->fail('Should not be able to encode a string'); 37 | } catch (\RuntimeException $e) { 38 | $this->assertContains("hello world", $e->getMessage()); 39 | } 40 | } 41 | 42 | /** 43 | * @dataProvider provideEncodedData 44 | */ 45 | public function testDecode($decodedValue, $encodedValue, $encodedValueWithChecksum, $requires64bit) 46 | { 47 | if ($requires64bit && 4 === PHP_INT_SIZE) { 48 | $this->markTestSkipped('Requires 64bit system (int size is '.PHP_INT_SIZE.')'); 49 | 50 | return; 51 | } 52 | 53 | $this->assertEquals($decodedValue, Crockford::decode($encodedValue)); 54 | $this->assertEquals($decodedValue, Crockford::decodeWithChecksum($encodedValueWithChecksum)); 55 | } 56 | 57 | public function testDecodeExtras() 58 | { 59 | $this->assertEquals('', Crockford::decode('')); 60 | $this->assertEquals('', Crockford::decode(null)); 61 | } 62 | 63 | public function testDecodeThrowsException() 64 | { 65 | try { 66 | Crockford::decodeWithChecksum('12'); 67 | 68 | $this->fails('Should throw an exception'); 69 | } catch (\RuntimeException $e) { 70 | $this->assertContains('is not correct value for', $e->getMessage()); 71 | $this->assertContains("'2'", $e->getMessage()); 72 | $this->assertContains("'1'", $e->getMessage()); 73 | } 74 | 75 | try { 76 | Crockford::decodeWithChecksum('U0'); 77 | 78 | $this->fails('Should throw an exception'); 79 | } catch (\RuntimeException $e) { 80 | $this->assertContains('contains invalid characters', $e->getMessage()); 81 | $this->assertContains("'U'", $e->getMessage()); 82 | } 83 | } 84 | 85 | public function testNormalize() 86 | { 87 | $this->assertEquals("ABC", Crockford::normalize("ABC")); 88 | $this->assertEquals("ABC", Crockford::normalize("A-B-C")); 89 | $this->assertEquals("ABC111100", Crockford::normalize("A-B-C-IiLlOo")); 90 | 91 | $this->assertEquals("ABC", Crockford::normalize("ABC", Crockford::NORMALIZE_ERRMODE_EXCEPTION)); 92 | } 93 | 94 | /** 95 | * @dataProvider provideNormalizeThrowsExceptionData 96 | */ 97 | public function testNormalizeThrowsException($input) 98 | { 99 | try { 100 | Crockford::normalize($input, Crockford::NORMALIZE_ERRMODE_EXCEPTION); 101 | 102 | $this->fail('Normalize would be required to do something, should have thrown an exception'); 103 | } catch (\RuntimeException $e) { 104 | $this->assertContains($input, $e->getMessage()); 105 | } 106 | } 107 | 108 | public function provideEncodedData() 109 | { 110 | return array( 111 | array(0, '0', '00', false), 112 | array(1, '1', '11', false), 113 | array(2, '2', '22', false), 114 | array(194, '62', '629', false), 115 | array(456789, 'DY2N', 'DY2NR', false), 116 | array(398373, 'C515', 'C515Z', false), 117 | array(519571, 'FVCK', 'FVCKH', false), 118 | array(3838385658376483, '3D2ZQ6TVC93', '3D2ZQ6TVC935', true), 119 | ); 120 | } 121 | 122 | public function provideNormalizeThrowsExceptionData() 123 | { 124 | return array( 125 | array('A-B-C'), 126 | array('abc'), 127 | array('A-B-C-IiLlOo'), 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Dflydev/Base32/Crockford/Crockford.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Crockford 20 | { 21 | const NORMALIZE_ERRMODE_SILENT = 0; 22 | const NORMALIZE_ERRMODE_EXCEPTION = 1; 23 | 24 | public static $symbols = array( 25 | '0', '1', '2', '3', '4', 26 | '5', '6', '7', '8', '9', 27 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 28 | 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 29 | 'T', 'V', 'W', 'X', 'Y', 'Z', 30 | '*', '~', '$', '=', 'U', 31 | ); 32 | 33 | public static $flippedSymbols = array( 34 | '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, 35 | '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9, 36 | 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 37 | 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 38 | 'J' => 18, 'K' => 19, 'M' => 20, 'N' => 21, 39 | 'P' => 22, 'Q' => 23, 'R' => 24, 'S' => 25, 40 | 'T' => 26, 'V' => 27, 'W' => 28, 'X' => 29, 41 | 'Y' => 30, 'Z' => 31, 42 | '*' => 32, '~' => 33, '$' => 34, '=' => 35, 'U' => 36, 43 | ); 44 | 45 | /** 46 | * Encode a number 47 | * 48 | * @param int $number 49 | * 50 | * @return string 51 | * 52 | * @throws \RuntimeException 53 | */ 54 | public static function encode($number) 55 | { 56 | if (!is_numeric($number)) { 57 | throw new \RuntimeException("Specified number '{$number}' is not numeric"); 58 | } 59 | 60 | if (!$number) { 61 | return 0; 62 | } 63 | 64 | $response = array(); 65 | while ($number) { 66 | $remainder = $number % 32; 67 | $number = (int) ($number/32); 68 | $response[] = static::$symbols[$remainder]; 69 | } 70 | 71 | return implode('', array_reverse($response)); 72 | } 73 | 74 | /** 75 | * Encode a number with checksum 76 | * 77 | * @param int $number 78 | * 79 | * @return string 80 | * 81 | * @throws \RuntimeException 82 | */ 83 | public static function encodeWithChecksum($number) 84 | { 85 | $encoded = static::encode($number); 86 | 87 | return $encoded . static::$symbols[$number % 37]; 88 | } 89 | 90 | /** 91 | * Decode a string 92 | * 93 | * @param string $string Encoded string 94 | * @param int $errmode Error mode 95 | * 96 | * @return int 97 | * 98 | * @throws \RuntimeException 99 | */ 100 | public static function decode($string, $errmode = self::NORMALIZE_ERRMODE_SILENT) 101 | { 102 | return static::internalDecode($string, $errmode); 103 | } 104 | 105 | /** 106 | * Decode a string with checksum 107 | * 108 | * @param string $string Encoded string 109 | * @param int $errmode Error mode 110 | * 111 | * @return int 112 | * 113 | * @throws \RuntimeException 114 | */ 115 | public static function decodeWithChecksum($string, $errmode = self::NORMALIZE_ERRMODE_SILENT) 116 | { 117 | $checksum = substr($string, (strlen($string) -1), 1); 118 | $string = substr($string, 0, strlen($string) - 1); 119 | 120 | $value = static::internalDecode($string, $errmode); 121 | $checksumValue = static::internalDecode($checksum, self::NORMALIZE_ERRMODE_EXCEPTION, true); 122 | 123 | if ($checksumValue !== ($value % 37)) { 124 | throw new \RuntimeException("Checksum symbol '$checksum' is not correct value for '$string'"); 125 | } 126 | 127 | return $value; 128 | } 129 | 130 | /** 131 | * Normalize a string 132 | * 133 | * @param string $string Encoded string 134 | * @param int $errmode Error mode 135 | * 136 | * @return string 137 | * 138 | * @throws \RuntimeException 139 | */ 140 | public static function normalize($string, $errmode = self::NORMALIZE_ERRMODE_SILENT) 141 | { 142 | $origString = $string; 143 | 144 | $string = strtoupper($string); 145 | if ($string !== $origString && $errmode) { 146 | throw new \RuntimeException("String '$origString' requires normalization"); 147 | } 148 | 149 | $string = str_replace('-', '', strtr($string, 'IiLlOo', '111100')); 150 | if ($string !== $origString && $errmode) { 151 | throw new \RuntimeException("String '$origString' requires normalization"); 152 | } 153 | 154 | return $string; 155 | } 156 | 157 | /** 158 | * Decode a string 159 | * 160 | * @param string $string Encoded string 161 | * @param int $errmode Error mode 162 | * @param bool $isChecksum Is encoded with a checksum? 163 | * 164 | * @return int 165 | * 166 | * @throws \RuntimeException 167 | */ 168 | protected static function internalDecode($string, $errmode = self::NORMALIZE_ERRMODE_SILENT, $isChecksum = false) 169 | { 170 | if ('' === $string) { 171 | return ''; 172 | } 173 | 174 | if (null === $string) { 175 | return ''; 176 | } 177 | 178 | $string = static::normalize($string, $errmode); 179 | 180 | if ($isChecksum) { 181 | $valid = '/^[A-Z0-9\*\~\$=U]$/'; 182 | } else { 183 | $valid = '/^[A-TV-Z0-9]+$/'; 184 | } 185 | 186 | if (!preg_match($valid, $string)) { 187 | throw new \RuntimeException("String '$string' contains invalid characters"); 188 | } 189 | 190 | $total = 0; 191 | foreach (str_split($string) as $symbol) { 192 | $total = $total * 32 + static::$flippedSymbols[$symbol]; 193 | } 194 | 195 | return $total; 196 | } 197 | } 198 | --------------------------------------------------------------------------------