mirror of
https://github.com/danog/tg-file-decoder.git
synced 2024-11-26 12:24:40 +01:00
Finalize
This commit is contained in:
parent
3c2194f2a5
commit
8481b5c66b
@ -22,10 +22,7 @@
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"danog\\Decoder\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
|
102
src/FileId.php
102
src/FileId.php
@ -111,7 +111,7 @@ final class FileId
|
||||
public static function fromBotAPI(string $fileId): self
|
||||
{
|
||||
$orig = $fileId;
|
||||
$fileId = rleDecode(base64urlDecode($fileId));
|
||||
$fileId = Tools::rleDecode(Tools::base64urlDecode($fileId));
|
||||
$version = \ord($fileId[\strlen($fileId) - 1]);
|
||||
$subVersion = $version === 4 ? \ord($fileId[\strlen($fileId) - 2]) : 0;
|
||||
|
||||
@ -126,16 +126,16 @@ final class FileId
|
||||
return $res;
|
||||
};
|
||||
|
||||
$typeId = unpackInt($read(4));
|
||||
$dc_id = unpackInt($read(4));
|
||||
$fileReference = $typeId & FILE_REFERENCE_FLAG ? readTLString($fileId) : null;
|
||||
$hasWebLocation = (bool) ($typeId & WEB_LOCATION_FLAG);
|
||||
$typeId &= ~FILE_REFERENCE_FLAG;
|
||||
$typeId &= ~WEB_LOCATION_FLAG;
|
||||
$typeId = Tools::unpackInt($read(4));
|
||||
$dc_id = Tools::unpackInt($read(4));
|
||||
$fileReference = $typeId & Tools::FILE_REFERENCE_FLAG ? Tools::readTLString($fileId) : null;
|
||||
$hasWebLocation = (bool) ($typeId & Tools::WEB_LOCATION_FLAG);
|
||||
$typeId &= ~Tools::FILE_REFERENCE_FLAG;
|
||||
$typeId &= ~Tools::WEB_LOCATION_FLAG;
|
||||
|
||||
if ($hasWebLocation) {
|
||||
$url = readTLString($fileId);
|
||||
$access_hash = unpackLong($read(8));
|
||||
$url = Tools::readTLString($fileId);
|
||||
$access_hash = Tools::unpackLong($read(8));
|
||||
return new self(
|
||||
$dc_id,
|
||||
FileIdType::from($typeId),
|
||||
@ -147,28 +147,28 @@ final class FileId
|
||||
subVersion: $subVersion
|
||||
);
|
||||
}
|
||||
$id = unpackLong($read(8));
|
||||
$access_hash = unpackLong($read(8));
|
||||
$id = Tools::unpackLong($read(8));
|
||||
$access_hash = Tools::unpackLong($read(8));
|
||||
|
||||
$volume_id = null;
|
||||
$local_id = null;
|
||||
$photoSizeSource = null;
|
||||
if ($typeId <= FileIdType::PHOTO->value) {
|
||||
if ($subVersion < 32) {
|
||||
$volume_id = unpackLong($read(8));
|
||||
$local_id = unpackInt($read(4));
|
||||
$volume_id = Tools::unpackLong($read(8));
|
||||
$local_id = Tools::unpackInt($read(4));
|
||||
}
|
||||
|
||||
/** @psalm-suppress MixedArgument */
|
||||
$photosize_source = PhotoSizeSourceType::from($subVersion >= 4 ? \unpack('V', $read(4))[1] : 0);
|
||||
switch ($photosize_source) {
|
||||
case PhotoSizeSourceType::LEGACY:
|
||||
$photoSizeSource = new PhotoSizeSourceLegacy(unpackLong($read(8)));
|
||||
$photoSizeSource = new PhotoSizeSourceLegacy(Tools::unpackLong($read(8)));
|
||||
break;
|
||||
case PhotoSizeSourceType::FULL_LEGACY:
|
||||
$volume_id = unpackLong($read(8));
|
||||
$photoSizeSource = new PhotoSizeSourceLegacy(unpackLong($read(8)));
|
||||
$local_id = unpackInt($read(4));
|
||||
$volume_id = Tools::unpackLong($read(8));
|
||||
$photoSizeSource = new PhotoSizeSourceLegacy(Tools::unpackLong($read(8)));
|
||||
$local_id = Tools::unpackInt($read(4));
|
||||
break;
|
||||
case PhotoSizeSourceType::THUMBNAIL:
|
||||
/** @var array{file_type: int, thumbnail_type: string} */
|
||||
@ -184,14 +184,14 @@ final class FileId
|
||||
? PhotoSizeSourceDialogPhotoSmall::class
|
||||
: PhotoSizeSourceDialogPhotoBig::class;
|
||||
$photoSizeSource = new $clazz(
|
||||
unpackLong($read(8)),
|
||||
unpackLong($read(8)),
|
||||
Tools::unpackLong($read(8)),
|
||||
Tools::unpackLong($read(8)),
|
||||
);
|
||||
break;
|
||||
case PhotoSizeSourceType::STICKERSET_THUMBNAIL:
|
||||
$photoSizeSource = new PhotoSizeSourceStickersetThumbnail(
|
||||
unpackLong($read(8)),
|
||||
unpackLong($read(8))
|
||||
Tools::unpackLong($read(8)),
|
||||
Tools::unpackLong($read(8))
|
||||
);
|
||||
break;
|
||||
case PhotoSizeSourceType::DIALOGPHOTO_BIG_LEGACY:
|
||||
@ -200,27 +200,27 @@ final class FileId
|
||||
? PhotoSizeSourceDialogPhotoSmall::class
|
||||
: PhotoSizeSourceDialogPhotoBig::class;
|
||||
$photoSizeSource = new $clazz(
|
||||
unpackLong($read(8)),
|
||||
unpackLong($read(8))
|
||||
Tools::unpackLong($read(8)),
|
||||
Tools::unpackLong($read(8))
|
||||
);
|
||||
|
||||
$volume_id = unpackLong($read(8));
|
||||
$local_id = unpackInt($read(4));
|
||||
$volume_id = Tools::unpackLong($read(8));
|
||||
$local_id = Tools::unpackInt($read(4));
|
||||
break;
|
||||
case PhotoSizeSourceType::STICKERSET_THUMBNAIL_LEGACY:
|
||||
$photoSizeSource = new PhotoSizeSourceStickersetThumbnail(
|
||||
unpackLong($read(8)),
|
||||
unpackLong($read(8)),
|
||||
Tools::unpackLong($read(8)),
|
||||
Tools::unpackLong($read(8)),
|
||||
);
|
||||
|
||||
$volume_id = unpackLong($read(8));
|
||||
$local_id = unpackInt($read(4));
|
||||
$volume_id = Tools::unpackLong($read(8));
|
||||
$local_id = Tools::unpackInt($read(4));
|
||||
break;
|
||||
case PhotoSizeSourceType::STICKERSET_THUMBNAIL_VERSION:
|
||||
$photoSizeSource = new PhotoSizeSourceStickersetThumbnailVersion(
|
||||
unpackLong($read(8)),
|
||||
unpackLong($read(8)),
|
||||
unpackInt($read(4))
|
||||
Tools::unpackLong($read(8)),
|
||||
Tools::unpackLong($read(8)),
|
||||
Tools::unpackInt($read(4))
|
||||
);
|
||||
break;
|
||||
}
|
||||
@ -253,25 +253,25 @@ final class FileId
|
||||
{
|
||||
$type = $this->type->value;
|
||||
if ($this->fileReference !== null) {
|
||||
$type |= FILE_REFERENCE_FLAG;
|
||||
$type |= Tools::FILE_REFERENCE_FLAG;
|
||||
}
|
||||
if ($this->url !== null) {
|
||||
$type |= WEB_LOCATION_FLAG;
|
||||
$type |= Tools::WEB_LOCATION_FLAG;
|
||||
}
|
||||
|
||||
$fileId = \pack('VV', $type, $this->dcId);
|
||||
if ($this->fileReference !== null) {
|
||||
$fileId .= packTLString($this->fileReference);
|
||||
$fileId .= Tools::packTLString($this->fileReference);
|
||||
}
|
||||
if ($this->url !== null) {
|
||||
$fileId .= packTLString($this->url);
|
||||
$fileId .= packLong($this->accessHash);
|
||||
return base64urlEncode(rleEncode($fileId));
|
||||
$fileId .= Tools::packTLString($this->url);
|
||||
$fileId .= Tools::packLong($this->accessHash);
|
||||
return Tools::base64urlEncode(Tools::rleEncode($fileId));
|
||||
}
|
||||
|
||||
\assert($this->id !== null);
|
||||
$fileId .= packLong($this->id);
|
||||
$fileId .= packLong($this->accessHash);
|
||||
$fileId .= Tools::packLong($this->id);
|
||||
$fileId .= Tools::packLong($this->accessHash);
|
||||
|
||||
if ($this->photoSizeSource !== null) {
|
||||
$photoSize = $this->photoSizeSource;
|
||||
@ -281,11 +281,11 @@ final class FileId
|
||||
if ($this->volumeId === null) {
|
||||
$writeExtra = true;
|
||||
$fileId .= \pack('V', PhotoSizeSourceType::LEGACY->value);
|
||||
$fileId .= packLong($photoSize->secret);
|
||||
$fileId .= Tools::packLong($photoSize->secret);
|
||||
} else {
|
||||
$fileId .= \pack('V', PhotoSizeSourceType::FULL_LEGACY->value);
|
||||
$fileId .= packLong($this->volumeId);
|
||||
$fileId .= packLong($photoSize->secret);
|
||||
$fileId .= Tools::packLong($this->volumeId);
|
||||
$fileId .= Tools::packLong($photoSize->secret);
|
||||
$fileId .= \pack('l', $this->localId);
|
||||
}
|
||||
break;
|
||||
@ -307,22 +307,22 @@ final class FileId
|
||||
: PhotoSizeSourceType::DIALOGPHOTO_BIG->value
|
||||
)
|
||||
);
|
||||
$fileId .= packLong($photoSize->dialogId);
|
||||
$fileId .= packLong($photoSize->dialogAccessHash);
|
||||
$fileId .= Tools::packLong($photoSize->dialogId);
|
||||
$fileId .= Tools::packLong($photoSize->dialogAccessHash);
|
||||
break;
|
||||
case $photoSize instanceof PhotoSizeSourceStickersetThumbnail:
|
||||
$writeExtra = $this->volumeId !== null;
|
||||
$fileId .= packLong($photoSize->stickerSetId);
|
||||
$fileId .= packLong($photoSize->stickerSetAccessHash);
|
||||
$fileId .= Tools::packLong($photoSize->stickerSetId);
|
||||
$fileId .= Tools::packLong($photoSize->stickerSetAccessHash);
|
||||
break;
|
||||
case $photoSize instanceof PhotoSizeSourceStickersetThumbnailVersion:
|
||||
$fileId .= packLong($photoSize->stickerSetId);
|
||||
$fileId .= packLong($photoSize->stickerSetAccessHash);
|
||||
$fileId .= Tools::packLong($photoSize->stickerSetId);
|
||||
$fileId .= Tools::packLong($photoSize->stickerSetAccessHash);
|
||||
$fileId .= \pack('l', $photoSize->stickerSetVersion);
|
||||
break;
|
||||
}
|
||||
if ($writeExtra && $this->volumeId !== null && $this->localId !== null) {
|
||||
$fileId .= packLong($this->volumeId);
|
||||
$fileId .= Tools::packLong($this->volumeId);
|
||||
$fileId .= \pack('l', $this->localId);
|
||||
}
|
||||
}
|
||||
@ -332,7 +332,7 @@ final class FileId
|
||||
}
|
||||
$fileId .= \chr($this->version);
|
||||
|
||||
return base64urlEncode(rleEncode($fileId));
|
||||
return Tools::base64urlEncode(Tools::rleEncode($fileId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
205
src/Tools.php
Normal file
205
src/Tools.php
Normal file
@ -0,0 +1,205 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace danog\Decoder;
|
||||
|
||||
\define('BIG_ENDIAN', \pack('L', 1) === \pack('N', 1));
|
||||
|
||||
final class Tools
|
||||
{
|
||||
public const WEB_LOCATION_FLAG = 1 << 24;
|
||||
public const FILE_REFERENCE_FLAG = 1 << 25;
|
||||
|
||||
/**
|
||||
* Unpack long properly, returns an actual number in any case.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string $field Field to unpack
|
||||
*/
|
||||
public static function unpackLong(string $field): int
|
||||
{
|
||||
/** @psalm-suppress MixedReturnStatement */
|
||||
return \unpack('q', BIG_ENDIAN ? \strrev($field) : $field)[1];
|
||||
}
|
||||
/**
|
||||
* Unpack integer.
|
||||
* @internal
|
||||
*
|
||||
* @param string $field Field to unpack
|
||||
*/
|
||||
public static function unpackInt(string $field): int
|
||||
{
|
||||
/** @psalm-suppress MixedReturnStatement */
|
||||
return \unpack('l', $field)[1];
|
||||
}
|
||||
/**
|
||||
* Pack string long.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public static function packLong(int $field): string
|
||||
{
|
||||
$res = \pack('q', $field);
|
||||
return BIG_ENDIAN ? \strrev($res) : $res;
|
||||
}
|
||||
/**
|
||||
* Base64URL decode.
|
||||
*
|
||||
* @param string $data Data to decode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public static function base64urlDecode(string $data): string
|
||||
{
|
||||
return \base64_decode(\str_pad(\strtr($data, '-_', '+/'), \strlen($data) % 4, '=', STR_PAD_RIGHT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64URL encode.
|
||||
*
|
||||
* @param string $data Data to encode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public static function base64urlEncode(string $data): string
|
||||
{
|
||||
return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-byte RLE decode.
|
||||
*
|
||||
* @param string $string Data to decode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public static function rleDecode(string $string): string
|
||||
{
|
||||
$new = '';
|
||||
$last = '';
|
||||
$null = "\0";
|
||||
foreach (\str_split($string) as $cur) {
|
||||
if ($last === $null) {
|
||||
$new .= \str_repeat($last, \ord($cur));
|
||||
$last = '';
|
||||
} else {
|
||||
$new .= $last;
|
||||
$last = $cur;
|
||||
}
|
||||
}
|
||||
$string = $new.$last;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-byte RLE encode.
|
||||
*
|
||||
* @param string $string Data to encode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public static function rleEncode(string $string): string
|
||||
{
|
||||
$new = '';
|
||||
$count = 0;
|
||||
$null = "\0";
|
||||
foreach (\str_split($string) as $cur) {
|
||||
if ($cur === $null) {
|
||||
++$count;
|
||||
} else {
|
||||
if ($count > 0) {
|
||||
$new .= $null.\chr($count);
|
||||
$count = 0;
|
||||
}
|
||||
$new .= $cur;
|
||||
}
|
||||
}
|
||||
if ($count > 0) {
|
||||
$new .= $null.\chr($count);
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positive modulo
|
||||
* Works just like the % (modulus) operator, only returns always a postive number.
|
||||
*
|
||||
* @param int $a A
|
||||
* @param int $b B
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return int Modulo
|
||||
*/
|
||||
public static function posmod(int $a, int $b): int
|
||||
{
|
||||
$resto = $a % $b;
|
||||
|
||||
return $resto < 0 ? $resto + \abs($b) : $resto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read TL string.
|
||||
*
|
||||
* @param resource $stream Byte stream
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
public static function readTLString(mixed $stream): string
|
||||
{
|
||||
$l = \ord(\stream_get_contents($stream, 1));
|
||||
if ($l > 254) {
|
||||
throw new \InvalidArgumentException("Length too big!");
|
||||
}
|
||||
if ($l === 254) {
|
||||
/** @var int */
|
||||
$long_len = \unpack('V', \stream_get_contents($stream, 3).\chr(0))[1];
|
||||
$x = \stream_get_contents($stream, $long_len);
|
||||
$resto = self::posmod(-$long_len, 4);
|
||||
if ($resto > 0) {
|
||||
\fseek($stream, $resto, SEEK_CUR);
|
||||
}
|
||||
} else {
|
||||
$x = $l ? \stream_get_contents($stream, $l) : '';
|
||||
$resto = self::posmod(-($l + 1), 4);
|
||||
if ($resto > 0) {
|
||||
\fseek($stream, $resto, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
\assert($x !== false);
|
||||
return $x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack TL string.
|
||||
*
|
||||
* @param string $string String
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function packTLString(string $string): string
|
||||
{
|
||||
$l = \strlen($string);
|
||||
$concat = '';
|
||||
if ($l <= 253) {
|
||||
$concat .= \chr($l);
|
||||
$concat .= $string;
|
||||
$concat .= \pack('@'.self::posmod(-$l - 1, 4));
|
||||
} else {
|
||||
$concat .= \chr(254);
|
||||
$concat .= \substr(\pack('V', $l), 0, 3);
|
||||
$concat .= $string;
|
||||
$concat .= \pack('@'.self::posmod(-$l, 4));
|
||||
}
|
||||
return $concat;
|
||||
}
|
||||
|
||||
}
|
@ -93,26 +93,26 @@ final class UniqueFileId
|
||||
{
|
||||
$fileId = \pack('V', $this->type->value);
|
||||
if ($this->url !== null) {
|
||||
$fileId .= packTLString($this->url);
|
||||
$fileId .= Tools::packTLString($this->url);
|
||||
} elseif ($this->type === UniqueFileIdType::PHOTO) {
|
||||
if ($this->volumeId !== null) {
|
||||
$fileId .= packLong($this->volumeId);
|
||||
$fileId .= Tools::packLong($this->volumeId);
|
||||
$fileId .= \pack('l', $this->localId);
|
||||
} elseif ($this->stickerSetId !== null) {
|
||||
\assert($this->subType !== null);
|
||||
$fileId .= \chr($this->subType);
|
||||
$fileId .= packLong($this->stickerSetId);
|
||||
$fileId .= Tools::packLong($this->stickerSetId);
|
||||
$fileId .= \pack('l', $this->stickerSetVersion);
|
||||
} else {
|
||||
\assert($this->subType !== null && $this->id !== null);
|
||||
$fileId .= packLong($this->id);
|
||||
$fileId .= Tools::packLong($this->id);
|
||||
$fileId .= \chr($this->subType);
|
||||
}
|
||||
} elseif ($this->id !== null) {
|
||||
$fileId .= packLong($this->id);
|
||||
$fileId .= Tools::packLong($this->id);
|
||||
}
|
||||
|
||||
return base64urlEncode(rleEncode($fileId));
|
||||
return Tools::base64urlEncode(Tools::rleEncode($fileId));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,7 +124,7 @@ final class UniqueFileId
|
||||
public static function fromUniqueBotAPI(string $fileId): self
|
||||
{
|
||||
$orig = $fileId;
|
||||
$fileId = rleDecode(base64urlDecode($fileId));
|
||||
$fileId = Tools::rleDecode(Tools::base64urlDecode($fileId));
|
||||
|
||||
/** @var int */
|
||||
$typeId = \unpack('V', $fileId)[1];
|
||||
@ -144,26 +144,26 @@ final class UniqueFileId
|
||||
\fwrite($res, $fileId);
|
||||
\fseek($res, 0);
|
||||
$fileId = $res;
|
||||
$url = readTLString($fileId);
|
||||
$url = Tools::readTLString($fileId);
|
||||
|
||||
$l = \fstat($fileId)['size'] - \ftell($fileId);
|
||||
\trigger_error("Unique file ID $orig has $l bytes of leftover data");
|
||||
} elseif (\strlen($fileId) === 12) {
|
||||
// Legacy photos
|
||||
$volume_id = unpackLong(\substr($fileId, 0, 8));
|
||||
$local_id = unpackInt(\substr($fileId, 8));
|
||||
$volume_id = Tools::unpackLong(\substr($fileId, 0, 8));
|
||||
$local_id = Tools::unpackInt(\substr($fileId, 8));
|
||||
} elseif (\strlen($fileId) === 9) {
|
||||
// Dialog photos/thumbnails
|
||||
$id = unpackLong($fileId);
|
||||
$id = Tools::unpackLong($fileId);
|
||||
$subType = \ord($fileId[8]);
|
||||
} elseif (\strlen($fileId) === 13) {
|
||||
// Stickerset ID/version
|
||||
$subType = \ord($fileId[0]);
|
||||
$sticker_set_id = unpackLong(\substr($fileId, 1, 8));
|
||||
$sticker_set_version = unpackInt(\substr($fileId, 9));
|
||||
$sticker_set_id = Tools::unpackLong(\substr($fileId, 1, 8));
|
||||
$sticker_set_version = Tools::unpackInt(\substr($fileId, 9));
|
||||
} elseif (\strlen($fileId) === 8) {
|
||||
// Any other document
|
||||
$id = unpackLong($fileId);
|
||||
$id = Tools::unpackLong($fileId);
|
||||
} else {
|
||||
$l = \strlen($fileId);
|
||||
\trigger_error("Unique file ID $orig has $l bytes of leftover data");
|
||||
|
@ -1,203 +0,0 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace danog\Decoder;
|
||||
|
||||
const WEB_LOCATION_FLAG = 1 << 24;
|
||||
const FILE_REFERENCE_FLAG = 1 << 25;
|
||||
|
||||
$BIG_ENDIAN = \pack('L', 1) === \pack('N', 1);
|
||||
|
||||
/**
|
||||
* Unpack long properly, returns an actual number in any case.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param string $field Field to unpack
|
||||
*/
|
||||
function unpackLong(string $field): int
|
||||
{
|
||||
global $BIG_ENDIAN; // Evil
|
||||
/** @psalm-suppress MixedReturnStatement */
|
||||
return \unpack('q', $BIG_ENDIAN ? \strrev($field) : $field)[1];
|
||||
}
|
||||
/**
|
||||
* Unpack integer.
|
||||
* @internal
|
||||
*
|
||||
* @param string $field Field to unpack
|
||||
*/
|
||||
function unpackInt(string $field): int
|
||||
{
|
||||
/** @psalm-suppress MixedReturnStatement */
|
||||
return \unpack('l', $field)[1];
|
||||
}
|
||||
/**
|
||||
* Pack string long.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
function packLong(int $field): string
|
||||
{
|
||||
global $BIG_ENDIAN; // Evil
|
||||
$res = \pack('q', $field);
|
||||
return $BIG_ENDIAN ? \strrev($res) : $res;
|
||||
}
|
||||
/**
|
||||
* Base64URL decode.
|
||||
*
|
||||
* @param string $data Data to decode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
function base64urlDecode(string $data): string
|
||||
{
|
||||
return \base64_decode(\str_pad(\strtr($data, '-_', '+/'), \strlen($data) % 4, '=', STR_PAD_RIGHT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64URL encode.
|
||||
*
|
||||
* @param string $data Data to encode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
function base64urlEncode(string $data): string
|
||||
{
|
||||
return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-byte RLE decode.
|
||||
*
|
||||
* @param string $string Data to decode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
function rleDecode(string $string): string
|
||||
{
|
||||
$new = '';
|
||||
$last = '';
|
||||
$null = "\0";
|
||||
foreach (\str_split($string) as $cur) {
|
||||
if ($last === $null) {
|
||||
$new .= \str_repeat($last, \ord($cur));
|
||||
$last = '';
|
||||
} else {
|
||||
$new .= $last;
|
||||
$last = $cur;
|
||||
}
|
||||
}
|
||||
$string = $new.$last;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-byte RLE encode.
|
||||
*
|
||||
* @param string $string Data to encode
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
function rleEncode(string $string): string
|
||||
{
|
||||
$new = '';
|
||||
$count = 0;
|
||||
$null = "\0";
|
||||
foreach (\str_split($string) as $cur) {
|
||||
if ($cur === $null) {
|
||||
++$count;
|
||||
} else {
|
||||
if ($count > 0) {
|
||||
$new .= $null.\chr($count);
|
||||
$count = 0;
|
||||
}
|
||||
$new .= $cur;
|
||||
}
|
||||
}
|
||||
if ($count > 0) {
|
||||
$new .= $null.\chr($count);
|
||||
}
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positive modulo
|
||||
* Works just like the % (modulus) operator, only returns always a postive number.
|
||||
*
|
||||
* @param int $a A
|
||||
* @param int $b B
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return int Modulo
|
||||
*/
|
||||
function posmod(int $a, int $b): int
|
||||
{
|
||||
$resto = $a % $b;
|
||||
|
||||
return $resto < 0 ? $resto + \abs($b) : $resto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read TL string.
|
||||
*
|
||||
* @param resource $stream Byte stream
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
*/
|
||||
function readTLString(mixed $stream): string
|
||||
{
|
||||
$l = \ord(\stream_get_contents($stream, 1));
|
||||
if ($l > 254) {
|
||||
throw new \InvalidArgumentException("Length too big!");
|
||||
}
|
||||
if ($l === 254) {
|
||||
/** @var int */
|
||||
$long_len = \unpack('V', \stream_get_contents($stream, 3).\chr(0))[1];
|
||||
$x = \stream_get_contents($stream, $long_len);
|
||||
$resto = posmod(-$long_len, 4);
|
||||
if ($resto > 0) {
|
||||
\fseek($stream, $resto, SEEK_CUR);
|
||||
}
|
||||
} else {
|
||||
$x = $l ? \stream_get_contents($stream, $l) : '';
|
||||
$resto = posmod(-($l + 1), 4);
|
||||
if ($resto > 0) {
|
||||
\fseek($stream, $resto, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
\assert($x !== false);
|
||||
return $x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack TL string.
|
||||
*
|
||||
* @param string $string String
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function packTLString(string $string): string
|
||||
{
|
||||
$l = \strlen($string);
|
||||
$concat = '';
|
||||
if ($l <= 253) {
|
||||
$concat .= \chr($l);
|
||||
$concat .= $string;
|
||||
$concat .= \pack('@'.posmod(-$l - 1, 4));
|
||||
} else {
|
||||
$concat .= \chr(254);
|
||||
$concat .= \substr(\pack('V', $l), 0, 3);
|
||||
$concat .= $string;
|
||||
$concat .= \pack('@'.posmod(-$l, 4));
|
||||
}
|
||||
return $concat;
|
||||
}
|
Loading…
Reference in New Issue
Block a user