8000 [String] allow passing a string of custom characters to ByteString::f… · symfony/symfony@184ee9c · GitHub
[go: up one dir, main page]

Skip to content

Commit 184ee9c

Browse files
committed
[String] allow passing a string of custom characters to ByteString::fromRandom
1 parent 4d8a4b6 commit 184ee9c

File tree

2 files changed

+45
-6
lines changed

2 files changed

+45
-6
lines changed

src/Symfony/Component/String/ByteString.php

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,58 @@
2525
*/
2626
class ByteString extends AbstractString
2727
{
28+
private const ALPHABET_ALPHANUMERIC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
29+
2830
public function __construct(string $string = '')
2931
{
3032
$this->string = $string;
3133
}
3234

33-
public static function fromRandom(int $length = 16): self
35+
public static function fromRandom(int $length = 16, string $alphabet = null): self
3436
{
35-
$string = '';
37+
if (0 === $length) {
38+
return new static('');
39+
}
40+
41+
if ($length < 0) {
42+
throw new \RuntimeException(\sprintf('Expected positive length value, got %s', $length));
43+
}
44+
45+
$alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
46+
$alphabet_size = \strlen($alphabet);
47+
$bits = (int)\ceil(\log($alphabet_size, 2.0));
48+
if ($bits <= 0 || $bits > 56) {
49+
throw new \RuntimeException('Expected $alphabet\'s length to be in [2^1, 2^56]');
50+
}
3651

37-
do {
38-
$string .= str_replace(['/', '+', '='], '', base64_encode(random_bytes($length)));
39-
} while (\strlen($string) < $length);
52+
$ret = '';
53+
while ($length > 0) {
54+
$urandom_length = (int)\ceil(2 * $length * $bits / 8.0);
55+
$data = \random_bytes($urandom_length);
56+
$unpacked_data = 0;
57+
$unpacked_bits = 0;
58+
for ($i = 0; $i < $urandom_length && $length > 0; ++$i) {
59+
// Unpack 8 bits
60+
$unpacked_data = ($unpacked_data << 8) | \ord($data[$i]);
61+
$unpacked_bits += 8;
62+
63+
// While we have enough bits to select a character from the alphabet, keep
64+
// consuming the random data
65+
for (; $unpacked_bits >= $bits && $length > 0; $unpacked_bits -= $bits) {
66+
$index = ($unpacked_data & ((1 << $bits) - 1));
67+
$unpacked_data >>= $bits;
68+
// Unfortunately, the alphabet size is not necessarily a power of two.
69+
// Worst case, it is 2^k + 1, which means we need (k+1) bits and we
70+
// have around a 50% chance of missing as k gets larger
71+
if ($index < $alphabet_size) {
72+
$ret .= $alphabet[$index];
73+
--$length;
74+
}
75+
}
76+
}
77+
}
4078

41-
return new static(substr($string, 0, $length));
79+
return new static($ret);
4280
}
4381

4482
public function bytesAt(int $offset): array

src/Symfony/Component/String/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
depending of the input string UTF-8 compliancy
1313
* added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()`
1414
* added `AbstractString::containsAny()`
15+
* allow passing a string of custom characters to ByteString::fromRandom
1516

1617
5.0.0
1718
-----

0 commit comments

Comments
 (0)
0