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": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"danog\\Decoder\\": "src/"
|
"danog\\Decoder\\": "src/"
|
||||||
},
|
}
|
||||||
"files": [
|
|
||||||
"src/functions.php"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
102
src/FileId.php
102
src/FileId.php
@ -111,7 +111,7 @@ final class FileId
|
|||||||
public static function fromBotAPI(string $fileId): self
|
public static function fromBotAPI(string $fileId): self
|
||||||
{
|
{
|
||||||
$orig = $fileId;
|
$orig = $fileId;
|
||||||
$fileId = rleDecode(base64urlDecode($fileId));
|
$fileId = Tools::rleDecode(Tools::base64urlDecode($fileId));
|
||||||
$version = \ord($fileId[\strlen($fileId) - 1]);
|
$version = \ord($fileId[\strlen($fileId) - 1]);
|
||||||
$subVersion = $version === 4 ? \ord($fileId[\strlen($fileId) - 2]) : 0;
|
$subVersion = $version === 4 ? \ord($fileId[\strlen($fileId) - 2]) : 0;
|
||||||
|
|
||||||
@ -126,16 +126,16 @@ final class FileId
|
|||||||
return $res;
|
return $res;
|
||||||
};
|
};
|
||||||
|
|
||||||
$typeId = unpackInt($read(4));
|
$typeId = Tools::unpackInt($read(4));
|
||||||
$dc_id = unpackInt($read(4));
|
$dc_id = Tools::unpackInt($read(4));
|
||||||
$fileReference = $typeId & FILE_REFERENCE_FLAG ? readTLString($fileId) : null;
|
$fileReference = $typeId & Tools::FILE_REFERENCE_FLAG ? Tools::readTLString($fileId) : null;
|
||||||
$hasWebLocation = (bool) ($typeId & WEB_LOCATION_FLAG);
|
$hasWebLocation = (bool) ($typeId & Tools::WEB_LOCATION_FLAG);
|
||||||
$typeId &= ~FILE_REFERENCE_FLAG;
|
$typeId &= ~Tools::FILE_REFERENCE_FLAG;
|
||||||
$typeId &= ~WEB_LOCATION_FLAG;
|
$typeId &= ~Tools::WEB_LOCATION_FLAG;
|
||||||
|
|
||||||
if ($hasWebLocation) {
|
if ($hasWebLocation) {
|
||||||
$url = readTLString($fileId);
|
$url = Tools::readTLString($fileId);
|
||||||
$access_hash = unpackLong($read(8));
|
$access_hash = Tools::unpackLong($read(8));
|
||||||
return new self(
|
return new self(
|
||||||
$dc_id,
|
$dc_id,
|
||||||
FileIdType::from($typeId),
|
FileIdType::from($typeId),
|
||||||
@ -147,28 +147,28 @@ final class FileId
|
|||||||
subVersion: $subVersion
|
subVersion: $subVersion
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$id = unpackLong($read(8));
|
$id = Tools::unpackLong($read(8));
|
||||||
$access_hash = unpackLong($read(8));
|
$access_hash = Tools::unpackLong($read(8));
|
||||||
|
|
||||||
$volume_id = null;
|
$volume_id = null;
|
||||||
$local_id = null;
|
$local_id = null;
|
||||||
$photoSizeSource = null;
|
$photoSizeSource = null;
|
||||||
if ($typeId <= FileIdType::PHOTO->value) {
|
if ($typeId <= FileIdType::PHOTO->value) {
|
||||||
if ($subVersion < 32) {
|
if ($subVersion < 32) {
|
||||||
$volume_id = unpackLong($read(8));
|
$volume_id = Tools::unpackLong($read(8));
|
||||||
$local_id = unpackInt($read(4));
|
$local_id = Tools::unpackInt($read(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @psalm-suppress MixedArgument */
|
/** @psalm-suppress MixedArgument */
|
||||||
$photosize_source = PhotoSizeSourceType::from($subVersion >= 4 ? \unpack('V', $read(4))[1] : 0);
|
$photosize_source = PhotoSizeSourceType::from($subVersion >= 4 ? \unpack('V', $read(4))[1] : 0);
|
||||||
switch ($photosize_source) {
|
switch ($photosize_source) {
|
||||||
case PhotoSizeSourceType::LEGACY:
|
case PhotoSizeSourceType::LEGACY:
|
||||||
$photoSizeSource = new PhotoSizeSourceLegacy(unpackLong($read(8)));
|
$photoSizeSource = new PhotoSizeSourceLegacy(Tools::unpackLong($read(8)));
|
||||||
break;
|
break;
|
||||||
case PhotoSizeSourceType::FULL_LEGACY:
|
case PhotoSizeSourceType::FULL_LEGACY:
|
||||||
$volume_id = unpackLong($read(8));
|
$volume_id = Tools::unpackLong($read(8));
|
||||||
$photoSizeSource = new PhotoSizeSourceLegacy(unpackLong($read(8)));
|
$photoSizeSource = new PhotoSizeSourceLegacy(Tools::unpackLong($read(8)));
|
||||||
$local_id = unpackInt($read(4));
|
$local_id = Tools::unpackInt($read(4));
|
||||||
break;
|
break;
|
||||||
case PhotoSizeSourceType::THUMBNAIL:
|
case PhotoSizeSourceType::THUMBNAIL:
|
||||||
/** @var array{file_type: int, thumbnail_type: string} */
|
/** @var array{file_type: int, thumbnail_type: string} */
|
||||||
@ -184,14 +184,14 @@ final class FileId
|
|||||||
? PhotoSizeSourceDialogPhotoSmall::class
|
? PhotoSizeSourceDialogPhotoSmall::class
|
||||||
: PhotoSizeSourceDialogPhotoBig::class;
|
: PhotoSizeSourceDialogPhotoBig::class;
|
||||||
$photoSizeSource = new $clazz(
|
$photoSizeSource = new $clazz(
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case PhotoSizeSourceType::STICKERSET_THUMBNAIL:
|
case PhotoSizeSourceType::STICKERSET_THUMBNAIL:
|
||||||
$photoSizeSource = new PhotoSizeSourceStickersetThumbnail(
|
$photoSizeSource = new PhotoSizeSourceStickersetThumbnail(
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
unpackLong($read(8))
|
Tools::unpackLong($read(8))
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case PhotoSizeSourceType::DIALOGPHOTO_BIG_LEGACY:
|
case PhotoSizeSourceType::DIALOGPHOTO_BIG_LEGACY:
|
||||||
@ -200,27 +200,27 @@ final class FileId
|
|||||||
? PhotoSizeSourceDialogPhotoSmall::class
|
? PhotoSizeSourceDialogPhotoSmall::class
|
||||||
: PhotoSizeSourceDialogPhotoBig::class;
|
: PhotoSizeSourceDialogPhotoBig::class;
|
||||||
$photoSizeSource = new $clazz(
|
$photoSizeSource = new $clazz(
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
unpackLong($read(8))
|
Tools::unpackLong($read(8))
|
||||||
);
|
);
|
||||||
|
|
||||||
$volume_id = unpackLong($read(8));
|
$volume_id = Tools::unpackLong($read(8));
|
||||||
$local_id = unpackInt($read(4));
|
$local_id = Tools::unpackInt($read(4));
|
||||||
break;
|
break;
|
||||||
case PhotoSizeSourceType::STICKERSET_THUMBNAIL_LEGACY:
|
case PhotoSizeSourceType::STICKERSET_THUMBNAIL_LEGACY:
|
||||||
$photoSizeSource = new PhotoSizeSourceStickersetThumbnail(
|
$photoSizeSource = new PhotoSizeSourceStickersetThumbnail(
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
);
|
);
|
||||||
|
|
||||||
$volume_id = unpackLong($read(8));
|
$volume_id = Tools::unpackLong($read(8));
|
||||||
$local_id = unpackInt($read(4));
|
$local_id = Tools::unpackInt($read(4));
|
||||||
break;
|
break;
|
||||||
case PhotoSizeSourceType::STICKERSET_THUMBNAIL_VERSION:
|
case PhotoSizeSourceType::STICKERSET_THUMBNAIL_VERSION:
|
||||||
$photoSizeSource = new PhotoSizeSourceStickersetThumbnailVersion(
|
$photoSizeSource = new PhotoSizeSourceStickersetThumbnailVersion(
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
unpackLong($read(8)),
|
Tools::unpackLong($read(8)),
|
||||||
unpackInt($read(4))
|
Tools::unpackInt($read(4))
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -253,25 +253,25 @@ final class FileId
|
|||||||
{
|
{
|
||||||
$type = $this->type->value;
|
$type = $this->type->value;
|
||||||
if ($this->fileReference !== null) {
|
if ($this->fileReference !== null) {
|
||||||
$type |= FILE_REFERENCE_FLAG;
|
$type |= Tools::FILE_REFERENCE_FLAG;
|
||||||
}
|
}
|
||||||
if ($this->url !== null) {
|
if ($this->url !== null) {
|
||||||
$type |= WEB_LOCATION_FLAG;
|
$type |= Tools::WEB_LOCATION_FLAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileId = \pack('VV', $type, $this->dcId);
|
$fileId = \pack('VV', $type, $this->dcId);
|
||||||
if ($this->fileReference !== null) {
|
if ($this->fileReference !== null) {
|
||||||
$fileId .= packTLString($this->fileReference);
|
$fileId .= Tools::packTLString($this->fileReference);
|
||||||
}
|
}
|
||||||
if ($this->url !== null) {
|
if ($this->url !== null) {
|
||||||
$fileId .= packTLString($this->url);
|
$fileId .= Tools::packTLString($this->url);
|
||||||
$fileId .= packLong($this->accessHash);
|
$fileId .= Tools::packLong($this->accessHash);
|
||||||
return base64urlEncode(rleEncode($fileId));
|
return Tools::base64urlEncode(Tools::rleEncode($fileId));
|
||||||
}
|
}
|
||||||
|
|
||||||
\assert($this->id !== null);
|
\assert($this->id !== null);
|
||||||
$fileId .= packLong($this->id);
|
$fileId .= Tools::packLong($this->id);
|
||||||
$fileId .= packLong($this->accessHash);
|
$fileId .= Tools::packLong($this->accessHash);
|
||||||
|
|
||||||
if ($this->photoSizeSource !== null) {
|
if ($this->photoSizeSource !== null) {
|
||||||
$photoSize = $this->photoSizeSource;
|
$photoSize = $this->photoSizeSource;
|
||||||
@ -281,11 +281,11 @@ final class FileId
|
|||||||
if ($this->volumeId === null) {
|
if ($this->volumeId === null) {
|
||||||
$writeExtra = true;
|
$writeExtra = true;
|
||||||
$fileId .= \pack('V', PhotoSizeSourceType::LEGACY->value);
|
$fileId .= \pack('V', PhotoSizeSourceType::LEGACY->value);
|
||||||
$fileId .= packLong($photoSize->secret);
|
$fileId .= Tools::packLong($photoSize->secret);
|
||||||
} else {
|
} else {
|
||||||
$fileId .= \pack('V', PhotoSizeSourceType::FULL_LEGACY->value);
|
$fileId .= \pack('V', PhotoSizeSourceType::FULL_LEGACY->value);
|
||||||
$fileId .= packLong($this->volumeId);
|
$fileId .= Tools::packLong($this->volumeId);
|
||||||
$fileId .= packLong($photoSize->secret);
|
$fileId .= Tools::packLong($photoSize->secret);
|
||||||
$fileId .= \pack('l', $this->localId);
|
$fileId .= \pack('l', $this->localId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -307,22 +307,22 @@ final class FileId
|
|||||||
: PhotoSizeSourceType::DIALOGPHOTO_BIG->value
|
: PhotoSizeSourceType::DIALOGPHOTO_BIG->value
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$fileId .= packLong($photoSize->dialogId);
|
$fileId .= Tools::packLong($photoSize->dialogId);
|
||||||
$fileId .= packLong($photoSize->dialogAccessHash);
|
$fileId .= Tools::packLong($photoSize->dialogAccessHash);
|
||||||
break;
|
break;
|
||||||
case $photoSize instanceof PhotoSizeSourceStickersetThumbnail:
|
case $photoSize instanceof PhotoSizeSourceStickersetThumbnail:
|
||||||
$writeExtra = $this->volumeId !== null;
|
$writeExtra = $this->volumeId !== null;
|
||||||
$fileId .= packLong($photoSize->stickerSetId);
|
$fileId .= Tools::packLong($photoSize->stickerSetId);
|
||||||
$fileId .= packLong($photoSize->stickerSetAccessHash);
|
$fileId .= Tools::packLong($photoSize->stickerSetAccessHash);
|
||||||
break;
|
break;
|
||||||
case $photoSize instanceof PhotoSizeSourceStickersetThumbnailVersion:
|
case $photoSize instanceof PhotoSizeSourceStickersetThumbnailVersion:
|
||||||
$fileId .= packLong($photoSize->stickerSetId);
|
$fileId .= Tools::packLong($photoSize->stickerSetId);
|
||||||
$fileId .= packLong($photoSize->stickerSetAccessHash);
|
$fileId .= Tools::packLong($photoSize->stickerSetAccessHash);
|
||||||
$fileId .= \pack('l', $photoSize->stickerSetVersion);
|
$fileId .= \pack('l', $photoSize->stickerSetVersion);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ($writeExtra && $this->volumeId !== null && $this->localId !== null) {
|
if ($writeExtra && $this->volumeId !== null && $this->localId !== null) {
|
||||||
$fileId .= packLong($this->volumeId);
|
$fileId .= Tools::packLong($this->volumeId);
|
||||||
$fileId .= \pack('l', $this->localId);
|
$fileId .= \pack('l', $this->localId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,7 +332,7 @@ final class FileId
|
|||||||
}
|
}
|
||||||
$fileId .= \chr($this->version);
|
$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);
|
$fileId = \pack('V', $this->type->value);
|
||||||
if ($this->url !== null) {
|
if ($this->url !== null) {
|
||||||
$fileId .= packTLString($this->url);
|
$fileId .= Tools::packTLString($this->url);
|
||||||
} elseif ($this->type === UniqueFileIdType::PHOTO) {
|
} elseif ($this->type === UniqueFileIdType::PHOTO) {
|
||||||
if ($this->volumeId !== null) {
|
if ($this->volumeId !== null) {
|
||||||
$fileId .= packLong($this->volumeId);
|
$fileId .= Tools::packLong($this->volumeId);
|
||||||
$fileId .= \pack('l', $this->localId);
|
$fileId .= \pack('l', $this->localId);
|
||||||
} elseif ($this->stickerSetId !== null) {
|
} elseif ($this->stickerSetId !== null) {
|
||||||
\assert($this->subType !== null);
|
\assert($this->subType !== null);
|
||||||
$fileId .= \chr($this->subType);
|
$fileId .= \chr($this->subType);
|
||||||
$fileId .= packLong($this->stickerSetId);
|
$fileId .= Tools::packLong($this->stickerSetId);
|
||||||
$fileId .= \pack('l', $this->stickerSetVersion);
|
$fileId .= \pack('l', $this->stickerSetVersion);
|
||||||
} else {
|
} else {
|
||||||
\assert($this->subType !== null && $this->id !== null);
|
\assert($this->subType !== null && $this->id !== null);
|
||||||
$fileId .= packLong($this->id);
|
$fileId .= Tools::packLong($this->id);
|
||||||
$fileId .= \chr($this->subType);
|
$fileId .= \chr($this->subType);
|
||||||
}
|
}
|
||||||
} elseif ($this->id !== null) {
|
} 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
|
public static function fromUniqueBotAPI(string $fileId): self
|
||||||
{
|
{
|
||||||
$orig = $fileId;
|
$orig = $fileId;
|
||||||
$fileId = rleDecode(base64urlDecode($fileId));
|
$fileId = Tools::rleDecode(Tools::base64urlDecode($fileId));
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
$typeId = \unpack('V', $fileId)[1];
|
$typeId = \unpack('V', $fileId)[1];
|
||||||
@ -144,26 +144,26 @@ final class UniqueFileId
|
|||||||
\fwrite($res, $fileId);
|
\fwrite($res, $fileId);
|
||||||
\fseek($res, 0);
|
\fseek($res, 0);
|
||||||
$fileId = $res;
|
$fileId = $res;
|
||||||
$url = readTLString($fileId);
|
$url = Tools::readTLString($fileId);
|
||||||
|
|
||||||
$l = \fstat($fileId)['size'] - \ftell($fileId);
|
$l = \fstat($fileId)['size'] - \ftell($fileId);
|
||||||
\trigger_error("Unique file ID $orig has $l bytes of leftover data");
|
\trigger_error("Unique file ID $orig has $l bytes of leftover data");
|
||||||
} elseif (\strlen($fileId) === 12) {
|
} elseif (\strlen($fileId) === 12) {
|
||||||
// Legacy photos
|
// Legacy photos
|
||||||
$volume_id = unpackLong(\substr($fileId, 0, 8));
|
$volume_id = Tools::unpackLong(\substr($fileId, 0, 8));
|
||||||
$local_id = unpackInt(\substr($fileId, 8));
|
$local_id = Tools::unpackInt(\substr($fileId, 8));
|
||||||
} elseif (\strlen($fileId) === 9) {
|
} elseif (\strlen($fileId) === 9) {
|
||||||
// Dialog photos/thumbnails
|
// Dialog photos/thumbnails
|
||||||
$id = unpackLong($fileId);
|
$id = Tools::unpackLong($fileId);
|
||||||
$subType = \ord($fileId[8]);
|
$subType = \ord($fileId[8]);
|
||||||
} elseif (\strlen($fileId) === 13) {
|
} elseif (\strlen($fileId) === 13) {
|
||||||
// Stickerset ID/version
|
// Stickerset ID/version
|
||||||
$subType = \ord($fileId[0]);
|
$subType = \ord($fileId[0]);
|
||||||
$sticker_set_id = unpackLong(\substr($fileId, 1, 8));
|
$sticker_set_id = Tools::unpackLong(\substr($fileId, 1, 8));
|
||||||
$sticker_set_version = unpackInt(\substr($fileId, 9));
|
$sticker_set_version = Tools::unpackInt(\substr($fileId, 9));
|
||||||
} elseif (\strlen($fileId) === 8) {
|
} elseif (\strlen($fileId) === 8) {
|
||||||
// Any other document
|
// Any other document
|
||||||
$id = unpackLong($fileId);
|
$id = Tools::unpackLong($fileId);
|
||||||
} else {
|
} else {
|
||||||
$l = \strlen($fileId);
|
$l = \strlen($fileId);
|
||||||
\trigger_error("Unique file ID $orig has $l bytes of leftover data");
|
\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