SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/TwoFactor/FixedBitNotation.php

  1. <?php
  2. /*
  3.  * Copyright (c) 2009-2013 Andre DeMarre
  4.  *
  5.  * Permission is hereby granted, free of charge, to any person obtaining a copy
  6.  * of this software and associated documentation files (the "Software"), to deal
  7.  * in the Software without restriction, including without limitation the rights
  8.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9.  * copies of the Software, and to permit persons to whom the Software is furnished
  10.  * to do so, subject to the following conditions:
  11.  *
  12.  * The above copyright notice and this permission notice shall be included in all
  13.  * copies or substantial portions of the Software.
  14.  *
  15.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21.  * THE SOFTWARE.
  22.  */
  23.  
  24. namespace Sprout\Helpers\TwoFactor;
  25.  
  26.  
  27. /**
  28.  * The FixedBitNotation class is for binary to text conversion. It
  29.  * can handle many encoding schemes, formally defined or not, that
  30.  * use a fixed number of bits to encode each character.
  31.  *
  32.  * See also:
  33.  * https://github.com/ademarre/binary-to-text-php/commit/d44968b55fada4c1fac0902f7595b23d2814b2f2
  34.  */
  35. class FixedBitNotation
  36. {
  37. protected $_chars;
  38. protected $_bitsPerCharacter;
  39. protected $_radix;
  40. protected $_rightPadFinalBits;
  41. protected $_padFinalGroup;
  42. protected $_padCharacter;
  43. protected $_charmap;
  44.  
  45. /**
  46.   * Constructor
  47.   *
  48.   * @param integer $bitsPerCharacter Bits to use for each encoded
  49.   * character
  50.   * @param string $chars Base character alphabet
  51.   * @param boolean $rightPadFinalBits How to encode last character
  52.   * @param boolean $padFinalGroup Add padding to end of encoded
  53.   * output
  54.   * @param string $padCharacter Character to use for padding
  55.   */
  56. public function __construct(
  57. $bitsPerCharacter, $chars = NULL, $rightPadFinalBits = FALSE,
  58. $padFinalGroup = FALSE, $padCharacter = '=')
  59. {
  60. // Ensure validity of $chars
  61. if (!is_string($chars) || ($charLength = strlen($chars)) < 2) {
  62. $chars =
  63. '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,';
  64. $charLength = 64;
  65. }
  66.  
  67. // Ensure validity of $bitsPerCharacter
  68. if ($bitsPerCharacter < 1) {
  69. // $bitsPerCharacter must be at least 1
  70. $bitsPerCharacter = 1;
  71. $radix = 2;
  72.  
  73. } elseif ($charLength < 1 << $bitsPerCharacter) {
  74. // Character length of $chars is too small for $bitsPerCharacter
  75. // Set $bitsPerCharacter to greatest acceptable value
  76. $bitsPerCharacter = 1;
  77. $radix = 2;
  78.  
  79. while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) {
  80. $bitsPerCharacter++;
  81. }
  82.  
  83. $radix >>= 1;
  84.  
  85. } elseif ($bitsPerCharacter > 8) {
  86. // $bitsPerCharacter must not be greater than 8
  87. $bitsPerCharacter = 8;
  88. $radix = 256;
  89.  
  90. } else {
  91. $radix = 1 << $bitsPerCharacter;
  92. }
  93.  
  94. $this->_chars = $chars;
  95. $this->_bitsPerCharacter = $bitsPerCharacter;
  96. $this->_radix = $radix;
  97. $this->_rightPadFinalBits = $rightPadFinalBits;
  98. $this->_padFinalGroup = $padFinalGroup;
  99. $this->_padCharacter = $padCharacter[0];
  100. }
  101.  
  102. /**
  103.   * Encode a string
  104.   *
  105.   * @param string $rawString Binary data to encode
  106.   * @return string
  107.   */
  108. public function encode($rawString)
  109. {
  110. // Unpack string into an array of bytes
  111. $bytes = unpack('C*', $rawString);
  112. $byteCount = count($bytes);
  113.  
  114. $encodedString = '';
  115. $byte = array_shift($bytes);
  116. $bitsRead = 0;
  117.  
  118. $chars = $this->_chars;
  119. $bitsPerCharacter = $this->_bitsPerCharacter;
  120. $rightPadFinalBits = $this->_rightPadFinalBits;
  121. $padFinalGroup = $this->_padFinalGroup;
  122. $padCharacter = $this->_padCharacter;
  123.  
  124. // Generate encoded output;
  125. // each loop produces one encoded character
  126. for ($c = 0; $c < $byteCount * 8 / $bitsPerCharacter; $c++) {
  127.  
  128. // Get the bits needed for this encoded character
  129. if ($bitsRead + $bitsPerCharacter > 8) {
  130. // Not enough bits remain in this byte for the current
  131. // character
  132. // Save the remaining bits before getting the next byte
  133. $oldBitCount = 8 - $bitsRead;
  134. $oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount);
  135. $newBitCount = $bitsPerCharacter - $oldBitCount;
  136.  
  137. if (!$bytes) {
  138. // Last bits; match final character and exit loop
  139. if ($rightPadFinalBits) $oldBits <<= $newBitCount;
  140. $encodedString .= $chars[$oldBits];
  141.  
  142. if ($padFinalGroup) {
  143. // Array of the lowest common multiples of
  144. // $bitsPerCharacter and 8, divided by 8
  145. $lcmMap = array(1 => 1, 2 => 1, 3 => 3, 4 => 1,
  146. 5 => 5, 6 => 3, 7 => 7, 8 => 1);
  147. $bytesPerGroup = $lcmMap[$bitsPerCharacter];
  148. $pads = $bytesPerGroup * 8 / $bitsPerCharacter
  149. - ceil((strlen($rawString) % $bytesPerGroup)
  150. * 8 / $bitsPerCharacter);
  151. $encodedString .= str_repeat($padCharacter[0], $pads);
  152. }
  153.  
  154. break;
  155. }
  156.  
  157. // Get next byte
  158. $byte = array_shift($bytes);
  159. $bitsRead = 0;
  160.  
  161. } else {
  162. $oldBitCount = 0;
  163. $newBitCount = $bitsPerCharacter;
  164. }
  165.  
  166. // Read only the needed bits from this byte
  167. $bits = $byte >> 8 - ($bitsRead + ($newBitCount));
  168. $bits ^= $bits >> $newBitCount << $newBitCount;
  169. $bitsRead += $newBitCount;
  170.  
  171. if ($oldBitCount) {
  172. // Bits come from seperate bytes, add $oldBits to $bits
  173. $bits = ($oldBits << $newBitCount) | $bits;
  174. }
  175.  
  176. $encodedString .= $chars[$bits];
  177. }
  178.  
  179. return $encodedString;
  180. }
  181.  
  182. /**
  183.   * Decode a string
  184.   *
  185.   * @param string $encodedString Data to decode
  186.   * @param boolean $caseSensitive
  187.   * @param boolean $strict Returns NULL if $encodedString contains
  188.   * an undecodable character
  189.   * @return string|NULL
  190.   */
  191. public function decode($encodedString, $caseSensitive = TRUE,
  192. $strict = FALSE)
  193. {
  194. if (!$encodedString || !is_string($encodedString)) {
  195. // Empty string, nothing to decode
  196. return '';
  197. }
  198.  
  199. $chars = $this->_chars;
  200. $bitsPerCharacter = $this->_bitsPerCharacter;
  201. $radix = $this->_radix;
  202. $rightPadFinalBits = $this->_rightPadFinalBits;
  203. $padFinalGroup = $this->_padFinalGroup;
  204. $padCharacter = $this->_padCharacter;
  205.  
  206. // Get index of encoded characters
  207. if ($this->_charmap) {
  208. $charmap = $this->_charmap;
  209.  
  210. } else {
  211. $charmap = array();
  212.  
  213. for ($i = 0; $i < $radix; $i++) {
  214. $charmap[$chars[$i]] = $i;
  215. }
  216.  
  217. $this->_charmap = $charmap;
  218. }
  219.  
  220. // The last encoded character is $encodedString[$lastNotatedIndex]
  221. $lastNotatedIndex = strlen($encodedString) - 1;
  222.  
  223. // Remove trailing padding characters
  224. while ($encodedString[$lastNotatedIndex] == $padCharacter[0]) {
  225. $encodedString = substr($encodedString, 0, $lastNotatedIndex);
  226. $lastNotatedIndex--;
  227. }
  228.  
  229. $rawString = '';
  230. $byte = 0;
  231. $bitsWritten = 0;
  232.  
  233. // Convert each encoded character to a series of unencoded bits
  234. for ($c = 0; $c <= $lastNotatedIndex; $c++) {
  235.  
  236. if (!isset($charmap[$encodedString[$c]]) && !$caseSensitive) {
  237. // Encoded character was not found; try other case
  238. if (isset($charmap[$cUpper
  239. = strtoupper($encodedString[$c])])) {
  240. $charmap[$encodedString[$c]] = $charmap[$cUpper];
  241.  
  242. } elseif (isset($charmap[$cLower
  243. = strtolower($encodedString[$c])])) {
  244. $charmap[$encodedString[$c]] = $charmap[$cLower];
  245. }
  246. }
  247.  
  248. if (isset($charmap[$encodedString[$c]])) {
  249. $bitsNeeded = 8 - $bitsWritten;
  250. $unusedBitCount = $bitsPerCharacter - $bitsNeeded;
  251.  
  252. // Get the new bits ready
  253. if ($bitsNeeded > $bitsPerCharacter) {
  254. // New bits aren't enough to complete a byte; shift them
  255. // left into position
  256. $newBits = $charmap[$encodedString[$c]] << $bitsNeeded
  257. - $bitsPerCharacter;
  258. $bitsWritten += $bitsPerCharacter;
  259.  
  260. } elseif ($c != $lastNotatedIndex || $rightPadFinalBits) {
  261. // Zero or more too many bits to complete a byte;
  262. // shift right
  263. $newBits = $charmap[$encodedString[$c]] >> $unusedBitCount;
  264. $bitsWritten = 8; //$bitsWritten += $bitsNeeded;
  265.  
  266. } else {
  267. // Final bits don't need to be shifted
  268. $newBits = $charmap[$encodedString[$c]];
  269. $bitsWritten = 8;
  270. }
  271.  
  272. $byte |= $newBits;
  273.  
  274. if ($bitsWritten == 8 || $c == $lastNotatedIndex) {
  275. // Byte is ready to be written
  276. $rawString .= pack('C', $byte);
  277.  
  278. if ($c != $lastNotatedIndex) {
  279. // Start the next byte
  280. $bitsWritten = $unusedBitCount;
  281. $byte = ($charmap[$encodedString[$c]]
  282. ^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten;
  283. }
  284. }
  285.  
  286. } elseif ($strict) {
  287. // Unable to decode character; abort
  288. return NULL;
  289. }
  290. }
  291.  
  292. return $rawString;
  293. }
  294. }
  295.