Bugfixes and added support for downloading only specified ranges of a file

This commit is contained in:
Daniil Gentili 2017-01-12 11:04:17 +01:00
parent 7343ae83cf
commit 3b8cc9d5ee
9 changed files with 64 additions and 29 deletions

View File

@ -341,7 +341,7 @@ The first parameter of these functions must always be a [messageMediaPhoto](http
$output_file_name = $MadelineProto->download_to_dir($message_media, '/tmp/dldir'); $output_file_name = $MadelineProto->download_to_dir($message_media, '/tmp/dldir');
$custom_output_file_name = $MadelineProto->download_to_file($message_media, '/tmp/dldir/customname.ext'); $custom_output_file_name = $MadelineProto->download_to_file($message_media, '/tmp/dldir/customname.ext');
$stream = fopen('php://output', 'w'); // Stream to browser like with echo $stream = fopen('php://output', 'w'); // Stream to browser like with echo
$MadelineProto->download_to_stream($message_media, $stream); $MadelineProto->download_to_stream($message_media, $stream, $cb, $offset, $endoffset); // offset and endoffset are optional parameters that specify the byte from which to start downloading and the byte where to stop downloading (the latter non-inclusive), if not specified default to 0 and the size of the file
``` ```

View File

@ -1,7 +1,3 @@
---
title: MadelineProto documentation
description: PHP implementation of telegram's MTProto protocol
---
# MadelineProto # MadelineProto
[![StyleCI](https://styleci.io/repos/61838413/shield)](https://styleci.io/repos/61838413) [![StyleCI](https://styleci.io/repos/61838413/shield)](https://styleci.io/repos/61838413)
[![Build Status](https://travis-ci.org/danog/MadelineProto.svg?branch=master)](https://travis-ci.org/danog/MadelineProto) [![Build Status](https://travis-ci.org/danog/MadelineProto.svg?branch=master)](https://travis-ci.org/danog/MadelineProto)
@ -345,7 +341,7 @@ The first parameter of these functions must always be a [messageMediaPhoto](http
$output_file_name = $MadelineProto->download_to_dir($message_media, '/tmp/dldir'); $output_file_name = $MadelineProto->download_to_dir($message_media, '/tmp/dldir');
$custom_output_file_name = $MadelineProto->download_to_file($message_media, '/tmp/dldir/customname.ext'); $custom_output_file_name = $MadelineProto->download_to_file($message_media, '/tmp/dldir/customname.ext');
$stream = fopen('php://output', 'w'); // Stream to browser like with echo $stream = fopen('php://output', 'w'); // Stream to browser like with echo
$MadelineProto->download_to_stream($message_media, $stream); $MadelineProto->download_to_stream($message_media, $stream, $cb, $offset, $endoffset); // offset and endoffset are optional parameters that specify the byte from which to start downloading and the byte where to stop downloading (the latter non-inclusive), if not specified default to 0 and the size of the file
``` ```

View File

@ -235,7 +235,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
{ {
foreach ($this->datacenter->sockets as $id => &$socket) { foreach ($this->datacenter->sockets as $id => &$socket) {
\danog\MadelineProto\Logger::log('Resetting session id and seq_no in DC '.$id.'...'); \danog\MadelineProto\Logger::log('Resetting session id and seq_no in DC '.$id.'...');
$socket->session_id = \phpseclib\Crypt\Random::string(8); $socket->session_id = \danog\MadelineProto\Tools::random(8);
$socket->seq_no = 0; $socket->seq_no = 0;
$socket->incoming_messages = []; $socket->incoming_messages = [];
$socket->outgoing_messages = []; $socket->outgoing_messages = [];
@ -275,7 +275,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
public function init_authorization() public function init_authorization()
{ {
if ($this->datacenter->session_id == null) { if ($this->datacenter->session_id == null) {
$this->datacenter->session_id = \phpseclib\Crypt\Random::string(8); $this->datacenter->session_id = \danog\MadelineProto\Tools::random(8);
} }
if ($this->datacenter->temp_auth_key == null || $this->datacenter->auth_key == null) { if ($this->datacenter->temp_auth_key == null || $this->datacenter->auth_key == null) {
if ($this->datacenter->auth_key == null) { if ($this->datacenter->auth_key == null) {

View File

@ -43,7 +43,7 @@ trait AuthKeyHandler
* Vector long $server_public_key_fingerprints : This is a list of public RSA key fingerprints * Vector long $server_public_key_fingerprints : This is a list of public RSA key fingerprints
* ] * ]
*/ */
$nonce = \phpseclib\Crypt\Random::string(16); $nonce = \danog\MadelineProto\Tools::random(16);
$ResPQ = $this->method_call('req_pq', $ResPQ = $this->method_call('req_pq',
[ [
'nonce' => $nonce, 'nonce' => $nonce,
@ -103,7 +103,7 @@ trait AuthKeyHandler
$p_bytes = \danog\PHP\Struct::pack('>I', (string) $p); $p_bytes = \danog\PHP\Struct::pack('>I', (string) $p);
$q_bytes = \danog\PHP\Struct::pack('>I', (string) $q); $q_bytes = \danog\PHP\Struct::pack('>I', (string) $q);
$new_nonce = \phpseclib\Crypt\Random::string(32); $new_nonce = \danog\MadelineProto\Tools::random(32);
$data_unserialized = [ $data_unserialized = [
'pq' => $pq_bytes, 'pq' => $pq_bytes,
@ -121,7 +121,7 @@ trait AuthKeyHandler
* Encrypt serialized object * Encrypt serialized object
*/ */
$sha_digest = sha1($p_q_inner_data, true); $sha_digest = sha1($p_q_inner_data, true);
$random_bytes = \phpseclib\Crypt\Random::string(255 - strlen($p_q_inner_data) - strlen($sha_digest)); $random_bytes = \danog\MadelineProto\Tools::random(255 - strlen($p_q_inner_data) - strlen($sha_digest));
$to_encrypt = $sha_digest.$p_q_inner_data.$random_bytes; $to_encrypt = $sha_digest.$p_q_inner_data.$random_bytes;
$encrypted_data = $this->key->encrypt($to_encrypt); $encrypted_data = $this->key->encrypt($to_encrypt);
@ -316,7 +316,7 @@ trait AuthKeyHandler
foreach ($this->range(0, $this->settings['max_tries']['authorization']) as $retry_id) { foreach ($this->range(0, $this->settings['max_tries']['authorization']) as $retry_id) {
\danog\MadelineProto\Logger::log('Generating b...'); \danog\MadelineProto\Logger::log('Generating b...');
$b = new \phpseclib\Math\BigInteger(\phpseclib\Crypt\Random::string(256), 256); $b = new \phpseclib\Math\BigInteger(\danog\MadelineProto\Tools::random(256), 256);
\danog\MadelineProto\Logger::log('Generating g_b...'); \danog\MadelineProto\Logger::log('Generating g_b...');
$g_b = $g->powMod($b, $dh_prime); $g_b = $g->powMod($b, $dh_prime);
@ -362,7 +362,7 @@ trait AuthKeyHandler
* encrypt client_DH_inner_data * encrypt client_DH_inner_data
*/ */
$data_with_sha = sha1($data, true).$data; $data_with_sha = sha1($data, true).$data;
$data_with_sha_padded = $data_with_sha.\phpseclib\Crypt\Random::string($this->posmod(-strlen($data_with_sha), 16)); $data_with_sha_padded = $data_with_sha.\danog\MadelineProto\Tools::random($this->posmod(-strlen($data_with_sha), 16));
$encrypted_data = $this->ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv); $encrypted_data = $this->ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
\danog\MadelineProto\Logger::log('Executing set_client_DH_params...'); \danog\MadelineProto\Logger::log('Executing set_client_DH_params...');
@ -481,7 +481,7 @@ trait AuthKeyHandler
public function bind_temp_auth_key($expires_in) public function bind_temp_auth_key($expires_in)
{ {
\danog\MadelineProto\Logger::log('Binding authorization keys...'); \danog\MadelineProto\Logger::log('Binding authorization keys...');
$nonce = \danog\PHP\Struct::unpack('<q', \phpseclib\Crypt\Random::string(8))[0]; $nonce = \danog\PHP\Struct::unpack('<q', \danog\MadelineProto\Tools::random(8))[0];
$expires_at = time() + $expires_in; $expires_at = time() + $expires_in;
$temp_auth_key_id = \danog\PHP\Struct::unpack('<q', $this->datacenter->temp_auth_key['id'])[0]; $temp_auth_key_id = \danog\PHP\Struct::unpack('<q', $this->datacenter->temp_auth_key['id'])[0];
$perm_auth_key_id = \danog\PHP\Struct::unpack('<q', $this->datacenter->auth_key['id'])[0]; $perm_auth_key_id = \danog\PHP\Struct::unpack('<q', $this->datacenter->auth_key['id'])[0];
@ -499,9 +499,9 @@ trait AuthKeyHandler
$message_id = \danog\PHP\Struct::pack('<Q', $int_message_id); $message_id = \danog\PHP\Struct::pack('<Q', $int_message_id);
$seq_no = 0; $seq_no = 0;
$encrypted_data = \phpseclib\Crypt\Random::string(16).$message_id.\danog\PHP\Struct::pack('<II', $seq_no, strlen($message_data)).$message_data; $encrypted_data = \danog\MadelineProto\Tools::random(16).$message_id.\danog\PHP\Struct::pack('<II', $seq_no, strlen($message_data)).$message_data;
$message_key = substr(sha1($encrypted_data, true), -16); $message_key = substr(sha1($encrypted_data, true), -16);
$padding = \phpseclib\Crypt\Random::string($this->posmod(-strlen($encrypted_data), 16)); $padding = \danog\MadelineProto\Tools::random($this->posmod(-strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->auth_key['auth_key']); list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->auth_key['auth_key']);
$encrypted_message = $this->datacenter->auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv); $encrypted_message = $this->datacenter->auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);

View File

@ -37,7 +37,7 @@ trait MessageHandler
$seq_no = $this->generate_seq_no($content_related); $seq_no = $this->generate_seq_no($content_related);
$encrypted_data = \danog\PHP\Struct::pack('<q', $this->datacenter->temp_auth_key['server_salt']).$this->datacenter->session_id.$message_id.\danog\PHP\Struct::pack('<II', $seq_no, strlen($message_data)).$message_data; $encrypted_data = \danog\PHP\Struct::pack('<q', $this->datacenter->temp_auth_key['server_salt']).$this->datacenter->session_id.$message_id.\danog\PHP\Struct::pack('<II', $seq_no, strlen($message_data)).$message_data;
$message_key = substr(sha1($encrypted_data, true), -16); $message_key = substr(sha1($encrypted_data, true), -16);
$padding = \phpseclib\Crypt\Random::string($this->posmod(-strlen($encrypted_data), 16)); $padding = \danog\MadelineProto\Tools::random($this->posmod(-strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->temp_auth_key['auth_key']); list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->temp_auth_key['auth_key']);
$message = $this->datacenter->temp_auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv); $message = $this->datacenter->temp_auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$this->datacenter->outgoing_messages[$int_message_id]['seq_no'] = $seq_no; $this->datacenter->outgoing_messages[$int_message_id]['seq_no'] = $seq_no;

View File

@ -25,12 +25,18 @@ trait UpdateHandler
public function get_updates_update_handler($update) public function get_updates_update_handler($update)
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
$this->updates[$this->updates_key++] = $update; $this->updates[$this->updates_key++] = $update;
//\danog\MadelineProto\Logger::log('Stored ', $update); //\danog\MadelineProto\Logger::log('Stored ', $update);
} }
public function get_updates($params = []) public function get_updates($params = [])
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
$time = microtime(true); $time = microtime(true);
$this->force_get_updates_difference(); $this->force_get_updates_difference();
$default_params = ['offset' => 0, 'limit' => null, 'timeout' => 0]; $default_params = ['offset' => 0, 'limit' => null, 'timeout' => 0];
@ -75,6 +81,9 @@ trait UpdateHandler
public function get_channel_difference($channel) public function get_channel_difference($channel)
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
if (!$this->get_channel_state($channel)['sync_loading']) { if (!$this->get_channel_state($channel)['sync_loading']) {
$this->get_channel_state($channel)['sync_loading'] = true; $this->get_channel_state($channel)['sync_loading'] = true;
$this->get_channel_state($channel)['pending_pts_updates'] = []; $this->get_channel_state($channel)['pending_pts_updates'] = [];
@ -133,6 +142,9 @@ trait UpdateHandler
public function get_updates_difference() public function get_updates_difference()
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
if (!$this->get_update_state()['sync_loading']) { if (!$this->get_update_state()['sync_loading']) {
$this->get_update_state()['sync_loading'] = true; $this->get_update_state()['sync_loading'] = true;
$this->get_update_state()['pending_pts_updates'] = []; $this->get_update_state()['pending_pts_updates'] = [];
@ -313,6 +325,9 @@ trait UpdateHandler
public function pop_pending_seq_update() public function pop_pending_seq_update()
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
$next_seq = $this->get_update_state()['seq'] + 1; $next_seq = $this->get_update_state()['seq'] + 1;
if (empty($this->get_update_state()['pending_seq_updates'][$next_seq]['updates'])) { if (empty($this->get_update_state()['pending_seq_updates'][$next_seq]['updates'])) {
return false; return false;
@ -332,6 +347,9 @@ trait UpdateHandler
public function pop_pending_pts_update($channel_id) public function pop_pending_pts_update($channel_id)
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
if ($channel_id === false) { if ($channel_id === false) {
$cur_state = &$this->get_update_state(); $cur_state = &$this->get_update_state();
} else { } else {
@ -363,6 +381,9 @@ trait UpdateHandler
public function handle_multiple_update($updates, $options = [], $channel = false) public function handle_multiple_update($updates, $options = [], $channel = false)
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
if ($channel === false) { if ($channel === false) {
foreach ($updates as $update) { foreach ($updates as $update) {
switch ($update['_']) { switch ($update['_']) {
@ -383,6 +404,9 @@ trait UpdateHandler
public function handle_update_messages($messages, $channel = false) public function handle_update_messages($messages, $channel = false)
{ {
if (!$this->settings['updates']['handle_updates']) {
return;
}
foreach ($messages as $message) { foreach ($messages as $message) {
$this->save_update(['_' => $channel == false ? 'updateNewMessage' : 'updateNewChannelMessage', 'message' => $message, 'pts' => $channel == false ? $this->get_update_state()['pts'] : $this->get_channel_state($channel)['pts'], 'pts_count' => 0]); $this->save_update(['_' => $channel == false ? 'updateNewMessage' : 'updateNewChannelMessage', 'message' => $message, 'pts' => $channel == false ? $this->get_update_state()['pts'] : $this->get_channel_state($channel)['pts'], 'pts_count' => 0]);
} }

View File

@ -252,16 +252,16 @@ trait TL
if ($current_argument['name'] == 'random_id') { if ($current_argument['name'] == 'random_id') {
switch ($current_argument['type']) { switch ($current_argument['type']) {
case 'long': case 'long':
$serialized .= \phpseclib\Crypt\Random::string(8); $serialized .= \danog\MadelineProto\Tools::random(8);
continue 2; continue 2;
case 'int': case 'int':
$serialized .= \phpseclib\Crypt\Random::string(4); $serialized .= \danog\MadelineProto\Tools::random(4);
continue 2; continue 2;
case 'Vector t': case 'Vector t':
if (isset($arguments['id'])) { if (isset($arguments['id'])) {
$serialized .= \danog\PHP\Struct::pack('<i', $this->constructors->find_by_predicate('vector')['id']); $serialized .= \danog\PHP\Struct::pack('<i', $this->constructors->find_by_predicate('vector')['id']);
$serialized .= \danog\PHP\Struct::pack('<i', count($arguments['id'])); $serialized .= \danog\PHP\Struct::pack('<i', count($arguments['id']));
$serialized .= \phpseclib\Crypt\Random::string(8 * count($arguments['id'])); $serialized .= \danog\MadelineProto\Tools::random(8 * count($arguments['id']));
continue 2; continue 2;
} }
} }

View File

@ -17,6 +17,10 @@ namespace danog\MadelineProto;
*/ */
trait Tools trait Tools
{ {
public static function random($length) {
if ($length === 0) return '';
return \danog\MadelineProto\Tools::random($length);
}
/** /**
* posmod(numeric,numeric) : numeric * posmod(numeric,numeric) : numeric
* Works just like the % (modulus) operator, only returns always a postive number. * Works just like the % (modulus) operator, only returns always a postive number.

View File

@ -424,7 +424,7 @@ trait FilesHandler
$part_num = 0; $part_num = 0;
$method = $file_size > 10 * 1024 * 1024 ? 'upload.saveBigFilePart' : 'upload.saveFilePart'; $method = $file_size > 10 * 1024 * 1024 ? 'upload.saveBigFilePart' : 'upload.saveFilePart';
$constructor = $file_size > 10 * 1024 * 1024 ? 'inputFileBig' : 'inputFile'; $constructor = $file_size > 10 * 1024 * 1024 ? 'inputFileBig' : 'inputFile';
$file_id = \danog\PHP\Struct::unpack('<q', \phpseclib\Crypt\Random::string(8))[0]; $file_id = \danog\PHP\Struct::unpack('<q', \danog\MadelineProto\Tools::random(8))[0];
$f = fopen($file, 'r'); $f = fopen($file, 'r');
fseek($f, 0); fseek($f, 0);
while (ftell($f) !== $file_size) { while (ftell($f) !== $file_size) {
@ -451,6 +451,7 @@ trait FilesHandler
public function get_download_info($message_media) public function get_download_info($message_media)
{ {
if (!isset($message_media['_']) && isset($message_media['InputFileLocation']) && isset($message_media['size'])) return $message_media;
$res = []; $res = [];
switch ($message_media['_']) { switch ($message_media['_']) {
case 'messageMediaPhoto': case 'messageMediaPhoto':
@ -460,6 +461,7 @@ trait FilesHandler
$res['name'] = $photo['location']['volume_id'].'_'.$photo['location']['local_id']; $res['name'] = $photo['location']['volume_id'].'_'.$photo['location']['local_id'];
$res['size'] = $photo['size']; $res['size'] = $photo['size'];
$res['InputFileLocation'] = ['_' => 'inputFileLocation', 'volume_id' => $photo['location']['volume_id'], 'local_id' => $photo['location']['local_id'], 'secret' => $photo['location']['secret']]; $res['InputFileLocation'] = ['_' => 'inputFileLocation', 'volume_id' => $photo['location']['volume_id'], 'local_id' => $photo['location']['local_id'], 'secret' => $photo['location']['secret']];
$res['mime'] = 'image/jpeg';
return $res; return $res;
@ -492,7 +494,7 @@ trait FilesHandler
} }
$res['name'] .= '_'.$message_media['document']['id']; $res['name'] .= '_'.$message_media['document']['id'];
$res['size'] = $message_media['document']['size']; $res['size'] = $message_media['document']['size'];
$res['mime'] = $message_media['document']['mime_type'];
$res['InputFileLocation'] = ['_' => 'inputDocumentFileLocation', 'id' => $message_media['document']['id'], 'access_hash' => $message_media['document']['access_hash'], 'version' => $message_media['document']['version']]; $res['InputFileLocation'] = ['_' => 'inputDocumentFileLocation', 'id' => $message_media['document']['id'], 'access_hash' => $message_media['document']['access_hash'], 'version' => $message_media['document']['version']];
return $res; return $res;
@ -506,7 +508,7 @@ trait FilesHandler
{ {
$info = $this->get_download_info($message_media); $info = $this->get_download_info($message_media);
return $this->download_to_file($message_media, $dir.'/'.$info['name'].$info['ext'], $cb); return $this->download_to_file($info, $dir.'/'.$info['name'].$info['ext'], $cb);
} }
public function download_to_file($message_media, $file, $cb = null) public function download_to_file($message_media, $file, $cb = null)
@ -515,13 +517,15 @@ trait FilesHandler
touch($file); touch($file);
} }
$stream = fopen($file, 'w'); $stream = fopen($file, 'w');
fseek($stream, filesize($file)); $info = $this->get_download_info($message_media);
$this->download_to_stream($message_media, $stream, $cb);
$this->download_to_stream($info, $stream, $cb, filesize($file), $info['size']);
return $file; return $file;
} }
public function download_to_stream($message_media, $stream, $cb = null) public function download_to_stream($message_media, $stream, $cb = null, $offset = 0, $end = -1)
{ {
if ($cb === null) { if ($cb === null) {
$cb = function ($percent) { $cb = function ($percent) {
@ -529,10 +533,17 @@ trait FilesHandler
}; };
} }
$info = $this->get_download_info($message_media); $info = $this->get_download_info($message_media);
$offset = ftell($stream); if ($end === -1) $end = $info['size'];
if (stream_get_meta_data($stream)['seekable']) fseek($stream, $offset);
$size = $end - $offset;
$part_size = 512 * 1024; $part_size = 512 * 1024;
while (fwrite($stream, $this->API->method_call('upload.getFile', ['location' => $info['InputFileLocation'], 'offset' => $offset += $part_size, 'limit' => $part_size], null, true)['bytes']) != false) { $percent = 0;
$cb(ftell($stream) * 100 / $info['size']); while ($percent < 100) {
$real_part_size = ($offset + $part_size > $end) ? $part_size - (($offset + $part_size) - $end) : $part_size;
\danog\MadelineProto\Logger::log($real_part_size, $offset);
fwrite($stream, $this->API->method_call('upload.getFile', ['location' => $info['InputFileLocation'], 'offset' => $offset, 'limit' => $real_part_size], null, true)['bytes']);
\danog\MadelineProto\Logger::log($offset, $size, ftell($stream));
$cb($percent = ($offset += $real_part_size) * 100 / $size);
} }
return true; return true;