diff --git a/CHANGELOG.md b/CHANGELOG.md
index c573cd3a93d..f4c4abff6ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
- Added a new `Phalcon\Mvc\Model\Binder::findBoundModel` method. Params fetched from cache are being added to `internalCache` class property in `Phalcon\Mvc\Model\Binder::getParamsFromCache`
- Added `Phalcon\Mvc\Model\Criteria::createBuilder` to create a query builder from criteria
- Added `dispatcher::beforeForward` event to allow forwarding request to the separated module [#121](https://github.com/phalcon/cphalcon/issues/121), [#12417](https://github.com/phalcon/cphalcon/issues/12417)
+- Added `Phalcon\Security\Random:base62` to provide the largest value that can safely be used in URLs without needing to take extra characters into consideration [#12105](https://github.com/phalcon/cphalcon/issues/12105)
- Fixed Dispatcher forwarding when handling exception [#11819](https://github.com/phalcon/cphalcon/issues/11819), [#12154](https://github.com/phalcon/cphalcon/issues/12154)
- Fixed params view scope for PHP 7 [#12648](https://github.com/phalcon/cphalcon/issues/12648)
- Fixed `Phalcon\Mvc\Micro::handle` to prevent attemps to send response twice [#12668](https://github.com/phalcon/cphalcon/pull/12668)
diff --git a/phalcon/security/random.zep b/phalcon/security/random.zep
index 719ea8a8a38..17b17761e77 100644
--- a/phalcon/security/random.zep
+++ b/phalcon/security/random.zep
@@ -52,6 +52,9 @@ namespace Phalcon\Security;
* echo $random->hex(12); // 95469d667475125208be45c4
* echo $random->hex(13); // 05475e8af4a34f8f743ab48761
*
+ * // Random base62 string
+ * echo $random->base62(); // z0RkwHfh8ErDM1xw
+ *
* // Random base64 string
* echo $random->base64(12); // XfIN81jGGuKkcE1E
* echo $random->base64(12); // 3rcq39QzGK9fUqh8
@@ -172,37 +175,44 @@ class Random
* If $len is not specified, 16 is assumed. It may be larger in future.
* The result may contain alphanumeric characters except 0, O, I and l.
*
- * It is similar to Base64 but has been modified to avoid both non-alphanumeric
+ * It is similar to `Phalcon\Security\Random:base64` but has been modified to avoid both non-alphanumeric
* characters and letters which might look ambiguous when printed.
*
- *
+ *
* $random = new \Phalcon\Security\Random();
*
* echo $random->base58(); // 4kUgL2pdQMSCQtjE
- *
+ *
*
- * @link https://en.wikipedia.org/wiki/Base58
+ * @see \Phalcon\Security\Random:base64
+ * @link https://en.wikipedia.org/wiki/Base58
* @throws Exception If secure random number generator is not available or unexpected partial read
*/
- public function base58(n = null) -> string
+ public function base58(int len = null) -> string
{
- var bytes, idx;
- string byteString = "",
- alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
-
- let bytes = unpack("C*", this->bytes(n));
-
- for idx in bytes {
- let idx = idx % 64;
-
- if idx >= 58 {
- let idx = this->number(57);
- }
-
- let byteString .= alphabet[(int) idx];
- }
+ return this->base("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 58, len);
+ }
- return byteString;
+ /**
+ * Generates a random base62 string
+ *
+ * If $len is not specified, 16 is assumed. It may be larger in future.
+ *
+ * It is similar to `Phalcon\Security\Random:base58` but has been modified to provide the largest value that can
+ * safely be used in URLs without needing to take extra characters into consideration because it is [A-Za-z0-9].
+ *
+ *< code>
+ * $random = new \Phalcon\Security\Random();
+ *
+ * echo $random->base62(); // z0RkwHfh8ErDM1xw
+ *
+ *
+ * @see \Phalcon\Security\Random:base58
+ * @throws Exception If secure random number generator is not available or unexpected partial read
+ */
+ public function base62(int len = null) -> string
+ {
+ return this->base("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 62, len);
}
/**
@@ -210,7 +220,7 @@ class Random
*
* If $len is not specified, 16 is assumed. It may be larger in future.
* The length of the result string is usually greater of $len.
- * Size formula: 4 *( $len / 3) and this need to be rounded up to a multiple of 4.
+ * Size formula: 4 * ($len / 3) and this need to be rounded up to a multiple of 4.
*
*
* $random = new \Phalcon\Security\Random();
@@ -343,4 +353,31 @@ class Random
return hexdec(array_shift(ret));
}
+
+ /**
+ * Generates a random string based on the number ($base) of characters ($alphabet).
+ *
+ * If $n is not specified, 16 is assumed. It may be larger in future.
+ *
+ * @throws Exception If secure random number generator is not available or unexpected partial read
+ */
+ protected function base(string alphabet, int base, n = null) -> string
+ {
+ var bytes, idx;
+ string byteString = "";
+
+ let bytes = unpack("C*", this->bytes(n));
+
+ for idx in bytes {
+ let idx = idx % 64;
+
+ if idx >= base {
+ let idx = this->number(base - 1);
+ }
+
+ let byteString .= alphabet[(int) idx];
+ }
+
+ return byteString;
+ }
}
diff --git a/tests/unit/Security/RandomTest.php b/tests/unit/Security/RandomTest.php
index 66afd68ab85..9f6badc6fef 100644
--- a/tests/unit/Security/RandomTest.php
+++ b/tests/unit/Security/RandomTest.php
@@ -106,16 +106,7 @@ public function testRandomBase58()
{
$this->specify(
"base58 does not generate a valid string",
- function () {
- $lens = [
- 2,
- 12,
- 16,
- 24,
- 48,
- 100
- ];
-
+ function ($len) {
$random = new Random();
$isValid = function ($base58) {
@@ -131,17 +122,69 @@ function () {
return (preg_match('#^[^'.join('', $alphabet).']+$#i', $base58) === 0);
};
- foreach ($lens as $len) {
- $actual = $random->base58($len);
+ $actual = $random->base58($len);
+ if ($len === null) {
+ expect(strlen($actual))->equals(16);
+ } else {
expect(strlen($actual))->equals($len);
- expect($isValid($actual))->true();
}
- $actual = $random->base58();
- expect(strlen($actual))->equals(16);
expect($isValid($actual))->true();
- }
+ },
+ [
+ 'examples' => [
+ [null],
+ [2],
+ [12],
+ [16],
+ [24],
+ [48],
+ [100],
+ ]
+ ]
+ );
+ }
+
+ /**
+ * Tests the random base62 generation
+ *
+ * @issue 12105
+ * @author Serghei Iakovlev
+ * @since 2017-05-21
+ */
+ public function testRandomBase62()
+ {
+ $this->specify(
+ 'base62 does not generate a valid string',
+ function ($len) {
+ $random = new Random();
+
+ $isValid = function ($base62) {
+ return (preg_match("#^[^a-z0-9]+$#i", $base62) === 0);
+ };
+
+ $actual = $random->base62($len);
+
+ if ($len === null) {
+ expect(strlen($actual))->equals(16);
+ } else {
+ expect(strlen($actual))->equals($len);
+ }
+
+ expect($isValid($actual))->true();
+ },
+ [
+ 'examples' => [
+ [null],
+ [2],
+ [12],
+ [16],
+ [24],
+ [48],
+ [100],
+ ]
+ ]
);
}
@@ -155,40 +198,34 @@ public function testRandomBase64()
{
$this->specify(
"base64 does not generate a valid string",
- function () {
- $lens = [
- 2,
- 12,
- 16,
- 24,
- 48,
- 100
- ];
-
+ function ($len) {
$random = new Random();
- $checkSize = function ($base64, $len) {
- // Size formula: 4 *( $len / 3) and this need to be rounded up to a multiple of 4.
- $formula = (round(4*($len/3))%4 === 0) ? round(4*($len/3)) : round((4*($len/3)+4/2)/4)*4;
-
- return strlen($base64) == $formula;
- };
-
$isValid = function ($base64) {
return (preg_match("#[^a-z0-9+_=/-]+#i", $base64) === 0);
};
- foreach ($lens as $len) {
- $actual = $random->base64($len);
+ $actual = $random->base64($len);
- expect($checkSize($actual, $len))->true();
- expect($isValid($actual))->true();
+ if ($len === null) {
+ expect($this->checkSize($actual, 16))->true();
+ } else {
+ expect($this->checkSize($actual, $len))->true();
}
- $actual = $random->base64();
- expect($checkSize($actual, 16))->true();
expect($isValid($actual))->true();
- }
+ },
+ [
+ 'examples' => [
+ [null],
+ [2],
+ [12],
+ [16],
+ [24],
+ [48],
+ [100],
+ ]
+ ]
);
}
@@ -202,34 +239,34 @@ public function testRandomBase64Safe()
{
$this->specify(
"base64Safe does not generate a valid string",
- function () {
- $lens = [
- 2,
- 12,
- 16,
- 24,
- 48,
- 100
- ];
-
+ function ($len, $padding, $pattern) {
$random = new Random();
- $isValid = function ($base64, $padding = false) {
- $pattern = $padding ? "a-z0-9_=-" : "a-z0-9_-";
+ $isValid = function ($base64) use ($pattern) {
return (preg_match("#[^$pattern]+#i", $base64) === 0);
};
- foreach ($lens as $len) {
- $actual = $random->base64Safe($len);
- expect($isValid($actual))->true();
- }
-
- $actual = $random->base64Safe();
+ $actual = $random->base64Safe($len, $padding);
expect($isValid($actual))->true();
-
- $actual = $random->base64Safe(null, true);
- expect($isValid($actual, true))->true();
- }
+ },
+ [
+ 'examples' => [
+ [null, false, 'a-z0-9_-' ],
+ [null, true, 'a-z0-9_=-'],
+ [2, false, 'a-z0-9_-' ],
+ [2, true, 'a-z0-9_=-'],
+ [12, false, 'a-z0-9_-' ],
+ [12, true, 'a-z0-9_=-'],
+ [16, false, 'a-z0-9_-' ],
+ [16, true, 'a-z0-9_=-'],
+ [24, false, 'a-z0-9_-' ],
+ [24, true, 'a-z0-9_=-'],
+ [48, false, 'a-z0-9_-' ],
+ [48, true, 'a-z0-9_=-'],
+ [100, false, 'a-z0-9_-' ],
+ [100, true, 'a-z0-9_=-'],
+ ]
+ ]
);
}
@@ -317,4 +354,23 @@ function () {
}
);
}
+
+ /**
+ * Size formula: 4 * ($n / 3) and this need to be rounded up to a multiple of 4.
+ *
+ * @param string $string
+ * @param int $n
+ *
+ * @return bool
+ */
+ protected function checkSize($string, $n)
+ {
+ if (round(4 * ($n / 3)) % 4 === 0) {
+ $len = round(4 * ($n / 3));
+ } else {
+ $len = round((4 * ($n / 3) + 4 / 2) / 4) * 4;
+ }
+
+ return strlen($string) == $len;
+ }
}