getFile(); } $headers = []; if (isset($_SERVER['HTTP_RANGE'])) { $headers['range'] = $_SERVER['HTTP_RANGE']; } $messageMedia = yield from $this->getDownloadInfo($messageMedia); $result = ResponseInfo::parseHeaders( $_SERVER['REQUEST_METHOD'], $headers, $messageMedia ); foreach ($result->getHeaders() as $key => $value) { if (\is_array($value)) { foreach ($value as $subValue) { \header("$key: $subValue", false); } } else { \header("$key: $value"); } } \http_response_code($result->getCode()); if (!\in_array($result->getCode(), [Status::OK, Status::PARTIAL_CONTENT])) { yield Tools::echo($result->getCodeExplanation()); } elseif ($result->shouldServe()) { if (\ob_get_level()) { \ob_end_flush(); \ob_implicit_flush(); } yield from $this->downloadToStream($messageMedia, \fopen('php://output', 'w'), $cb, ...$result->getServeRange()); } } /** * Download file to stream. * * @param mixed $messageMedia File to download * @param mixed|FileCallbackInterface $stream Stream where to download file * @param callable $cb Callback (DEPRECATED, use FileCallbackInterface) * @param int $offset Offset where to start downloading * @param int $end Offset where to end download * * @return \Generator * * @psalm-return \Generator|\Amp\Promise|mixed, mixed, mixed> */ public function downloadToStream($messageMedia, $stream, $cb = null, int $offset = 0, int $end = -1): \Generator { $messageMedia = yield from $this->getDownloadInfo($messageMedia); if (\is_object($stream) && $stream instanceof FileCallbackInterface) { $cb = $stream; $stream = $stream->getFile(); } /** @var $stream \Amp\ByteStream\OutputStream */ if (!\is_object($stream)) { $stream = new ResourceOutputStream($stream); } if (!$stream instanceof OutputStream) { throw new Exception("Invalid stream provided"); } $seekable = false; if (\method_exists($stream, 'seek')) { try { yield $stream->seek($offset); $seekable = true; } catch (StreamException $e) { } } $callable = static function (string $payload, int $offset) use ($stream, $seekable): \Generator { if ($seekable) { while ($stream->tell() !== $offset) { yield $stream->seek($offset); } } return yield $stream->write($payload); }; return yield from $this->downloadToCallable($messageMedia, $callable, $cb, $seekable, $offset, $end); } /** * Download file to amphp/http-server response. * * Supports HEAD requests and content-ranges for parallel and resumed downloads. * * @param array|string $messageMedia File to download * @param ServerRequest $request Request * @param callable $cb Status callback (can also use FileCallback) * * @return \Generator Returned response */ public function downloadToResponse($messageMedia, ServerRequest $request, callable $cb = null): \Generator { if (\is_object($messageMedia) && $messageMedia instanceof FileCallbackInterface) { $cb = $messageMedia; $messageMedia = $messageMedia->getFile(); } $messageMedia = yield from $this->getDownloadInfo($messageMedia); $result = ResponseInfo::parseHeaders( $request->getMethod(), \array_map(fn (array $headers) => $headers[0], $request->getHeaders()), $messageMedia ); $body = null; if ($result->shouldServe()) { $body = new IteratorStream( new Producer( function (callable $emit) use (&$messageMedia, &$cb, &$result) { $emit = static function (string $payload) use ($emit): \Generator { yield $emit($payload); return \strlen($payload); }; yield Tools::call($this->downloadToCallable($messageMedia, $emit, $cb, false, ...$result->getServeRange())); } ) ); } elseif (!\in_array($result->getCode(), [Status::OK, Status::PARTIAL_CONTENT])) { $body = $result->getCodeExplanation(); } $response = new Response($result->getCode(), $result->getHeaders(), $body); if ($result->shouldServe() && !empty($result->getHeaders()['Content-Length'])) { $response->setHeader('content-length', $result->getHeaders()['Content-Length']); } return $response; } /** * Upload file to secret chat. * * @param FileCallbackInterface|string|array $file File, URL or Telegram file to upload * @param string $fileName File name * @param callable $cb Callback (DEPRECATED, use FileCallbackInterface) * * @return \Generator * * @psalm-return \Generator|array, mixed, mixed> */ public function uploadEncrypted($file, string $fileName = '', $cb = null): \Generator { return $this->upload($file, $fileName, $cb, true); } /** * Upload file. * * @param FileCallbackInterface|string|array $file File, URL or Telegram file to upload * @param string $fileName File name * @param callable $cb Callback (DEPRECATED, use FileCallbackInterface) * @param boolean $encrypted Whether to encrypt file for secret chats * * @return \Generator * * @psalm-return \Generator|\Amp\Promise<\Amp\Ipc\Sync\ChannelledSocket>|\Amp\Promise|\Amp\Promise|\Amp\Promise|\danog\MadelineProto\Stream\StreamInterface|array|int|mixed, mixed, mixed> */ public function upload($file, string $fileName = '', $cb = null, bool $encrypted = false): \Generator { if (\is_object($file) && $file instanceof FileCallbackInterface) { $cb = $file; $file = $file->getFile(); } if (\is_string($file) || \is_object($file) && \method_exists($file, '__toString')) { if (\filter_var($file, FILTER_VALIDATE_URL)) { return yield from $this->uploadFromUrl($file, 0, $fileName, $cb, $encrypted); } } elseif (\is_array($file)) { return yield from $this->uploadFromTgfile($file, $cb, $encrypted); } if (\is_resource($file) || (\is_object($file) && $file instanceof InputStream)) { return yield from $this->uploadFromStream($file, 0, '', $fileName, $cb, $encrypted); } if (!$this->settings->getFiles()->getAllowAutomaticUpload()) { return yield from $this->uploadFromUrl($file, 0, $fileName, $cb, $encrypted); } $file = Tools::absolute($file); if (!yield exists($file)) { throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['file_not_exist']); } if (empty($fileName)) { $fileName = \basename($file); } StatCacheAsync::clear($file); $size = (yield statAsync($file))['size']; if ($size > 512 * 1024 * 4000) { throw new \danog\MadelineProto\Exception('Given file is too big!'); } $stream = yield open($file, 'rb'); $mime = $this->getMimeFromFile($file); try { return yield from $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted); } finally { yield $stream->close(); } } /** * Upload file from stream. * * @param mixed $stream PHP resource or AMPHP async stream * @param integer $size File size * @param string $mime Mime type * @param string $fileName File name * @param callable $cb Callback (DEPRECATED, use FileCallbackInterface) * @param boolean $encrypted Whether to encrypt file for secret chats * * @return \Generator * * @psalm-return \Generator|\Amp\Promise|\danog\MadelineProto\Stream\StreamInterface|array|int|mixed, mixed, mixed> */ public function uploadFromStream($stream, int $size, string $mime, string $fileName = '', $cb = null, bool $encrypted = false): \Generator { if (\is_object($stream) && $stream instanceof FileCallbackInterface) { $cb = $stream; $stream = $stream->getFile(); } /* @var $stream \Amp\ByteStream\OutputStream */ if (!\is_object($stream)) { $stream = new ResourceInputStream($stream); } if (!$stream instanceof InputStream) { throw new Exception("Invalid stream provided"); } $seekable = false; if (\method_exists($stream, 'seek')) { try { yield $stream->seek(0); $seekable = true; } catch (StreamException $e) { } } $created = false; if ($stream instanceof Handle) { $callable = static function (int $offset, int $size) use ($stream, $seekable): \Generator { if ($seekable) { while ($stream->tell() !== $offset) { yield $stream->seek($offset); } } return yield $stream->read($size); }; } else { if (!$stream instanceof BufferedRawStream) { $ctx = (new ConnectionContext())->addStream(PremadeStream::class, $stream)->addStream(SimpleBufferedRawStream::class); $stream = (yield from $ctx->getStream()); $created = true; } $callable = static function (int $offset, int $size) use ($stream): \Generator { $reader = yield $stream->getReadBuffer($l); try { return yield $reader->bufferRead($size); } catch (\danog\MadelineProto\NothingInTheSocketException $e) { $reader = yield $stream->getReadBuffer($size); return yield $reader->bufferRead($size); } }; $seekable = false; } if (!$size && $seekable && \method_exists($stream, 'tell')) { yield $stream->seek(0, \SEEK_END); $size = yield $stream->tell(); yield $stream->seek(0); } elseif (!$size) { $this->logger->logger("No content length for stream, caching first"); $body = $stream; $stream = new BlockingFile(\fopen('php://temp', 'r+b'), 'php://temp', 'r+b'); while (null !== ($chunk = yield $body->read())) { yield $stream->write($chunk); } $size = $stream->tell(); if (!$size) { throw new Exception('Wrong size!'); } yield $stream->seek(0); return yield from $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted); } $res = (yield from $this->uploadFromCallable($callable, $size, $mime, $fileName, $cb, $seekable, $encrypted)); if ($created) { $stream->disconnect(); } return $res; } }