From ab797b4dffec07facdf249429ae829778ab031fe Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 20 Apr 2018 21:34:51 +0400 Subject: [PATCH] Preprocess images before passport upload. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/lang/lang_hardcoded.h | 4 + .../passport/passport_panel_controller.cpp | 18 ++++ .../passport/passport_panel_controller.h | 3 + .../passport/passport_panel_edit_scans.cpp | 82 +++++++++++++++++-- .../passport/passport_panel_edit_scans.h | 10 ++- 6 files changed, 114 insertions(+), 6 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a97a9600d..cd5feff9a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1612,6 +1612,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_passport_stop" = "Stop"; "lng_passport_restart_sure" = "Unexpected error has occurred. Perhaps some changes were done from a different Telegram application. Would you like to restart this authorization?"; "lng_passport_restart" = "Restart"; +"lng_passport_error_too_large" = "This file is too large."; +"lng_passport_error_bad_size" = "This image has bad dimensions."; +"lng_passport_error_cant_read" = "Can't read this file. Please choose an image."; // Wnd specific diff --git a/Telegram/SourceFiles/lang/lang_hardcoded.h b/Telegram/SourceFiles/lang/lang_hardcoded.h index 14249bc4a..9ad20a585 100644 --- a/Telegram/SourceFiles/lang/lang_hardcoded.h +++ b/Telegram/SourceFiles/lang/lang_hardcoded.h @@ -54,5 +54,9 @@ inline QString PassportCorruptedResetSure() { return qsl("Are you sure you want to reset your Telegram Passport data?"); } +inline QString UnknownSecureScanError() { + return qsl("Unknown scan read error."); +} + } // namespace Hard } // namespace Lang diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 2d1173a43..959f9e89a 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -843,9 +843,27 @@ void PanelController::editWithUpload(int index, int documentIndex) { base::take(_scopeDocumentTypeBox); editScope(index, documentIndex); uploadScan(std::move(content)); + }, [=](ReadScanError error) { + readScanError(error); }); } +void PanelController::readScanError(ReadScanError error) { + show(Box([&] { + switch (error) { + case ReadScanError::FileTooLarge: + return lang(lng_passport_error_too_large); + case ReadScanError::BadImageSize: + return lang(lng_passport_error_bad_size); + case ReadScanError::CantReadImage: + return lang(lng_passport_error_cant_read); + case ReadScanError::Unknown: + return Lang::Hard::UnknownSecureScanError(); + } + Unexpected("Error type in PanelController::readScanError."); + }())); +} + void PanelController::editScope(int index, int documentIndex) { Expects(_panel != nullptr); Expects(index >= 0 && index < _scopes.size()); diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.h b/Telegram/SourceFiles/passport/passport_panel_controller.h index a685e94f9..71784d31d 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.h +++ b/Telegram/SourceFiles/passport/passport_panel_controller.h @@ -18,6 +18,8 @@ class Panel; struct EditDocumentScheme; struct EditContactScheme; +enum class ReadScanError; + EditDocumentScheme GetDocumentScheme( Scope::Type type, base::optional scansType = base::none); @@ -84,6 +86,7 @@ public: void restoreSelfie(); rpl::producer scanUpdated() const; rpl::producer saveErrors() const; + void readScanError(ReadScanError error); base::optional> deleteValueLabel() const; void deleteValue(); diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp index 964845576..d79fc2def 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.cpp @@ -20,9 +20,49 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" #include "lang/lang_keys.h" #include "boxes/abstract_box.h" +#include "storage/storage_media_prepare.h" #include "styles/style_passport.h" namespace Passport { +namespace { + +constexpr auto kMaxDimensions = 2048; +constexpr auto kMaxSize = 10 * 1024 * 1024; +constexpr auto kJpegQuality = 89; + +static_assert(kMaxSize <= UseBigFilesFrom); + +base::variant ProcessImage(QByteArray &&bytes) { + auto image = App::readImage(base::take(bytes)); + if (image.isNull()) { + return ReadScanError::CantReadImage; + } else if (!Storage::ValidateThumbDimensions(image.width(), image.height())) { + return ReadScanError::BadImageSize; + } + if (std::max(image.width(), image.height()) > kMaxDimensions) { + image = std::move(image).scaled( + kMaxDimensions, + kMaxDimensions, + Qt::KeepAspectRatio, + Qt::SmoothTransformation); + } + auto result = QByteArray(); + { + QBuffer buffer(&result); + if (!image.save(&buffer, QByteArray("JPG"), kJpegQuality)) { + return ReadScanError::Unknown; + } + base::take(image); + } + if (result.isEmpty()) { + return ReadScanError::Unknown; + } else if (result.size() > kMaxSize) { + return ReadScanError::FileTooLarge; + } + return result; +} + +} // namespace class ScanButton : public Ui::AbstractButton { public: @@ -496,39 +536,71 @@ void EditScans::chooseScan() { } ChooseScan(this, [=](QByteArray &&content) { _controller->uploadScan(std::move(content)); + }, [=](ReadScanError error) { + _controller->readScanError(error); }); } void EditScans::chooseSelfie() { ChooseScan(this, [=](QByteArray &&content) { _controller->uploadSelfie(std::move(content)); + }, [=](ReadScanError error) { + _controller->readScanError(error); }); } void EditScans::ChooseScan( QPointer parent, - base::lambda callback) { + base::lambda doneCallback, + base::lambda errorCallback) { Expects(parent != nullptr); const auto filter = FileDialog::AllFilesFilter() + qsl(";;Image files (*") + cImgExtensions().join(qsl(" *")) + qsl(")"); - const auto guardedCallback = base::lambda_guarded(parent, callback); + const auto guardedCallback = base::lambda_guarded(parent, doneCallback); + const auto guardedError = base::lambda_guarded(parent, errorCallback); + const auto onMainCallback = [=](QByteArray content) { + crl::on_main([=, bytes = std::move(content)]() mutable { + guardedCallback(std::move(bytes)); + }); + }; + const auto onMainError = [=](ReadScanError error) { + crl::on_main([=] { + guardedError(error); + }); + }; + const auto processImage = [=](QByteArray &&content) { + crl::async([=, bytes = std::move(content)]() mutable { + auto result = ProcessImage(std::move(bytes)); + if (const auto error = base::get_if(&result)) { + onMainError(*error); + } else { + auto content = base::get_if(&result); + Assert(content != nullptr); + onMainCallback(std::move(*content)); + } + }); + }; const auto processFile = [=](FileDialog::OpenResult &&result) { if (result.paths.size() == 1) { auto content = [&] { QFile f(result.paths.front()); - if (!f.open(QIODevice::ReadOnly)) { + if (f.size() > App::kImageSizeLimit) { + guardedError(ReadScanError::FileTooLarge); + return QByteArray(); + } else if (!f.open(QIODevice::ReadOnly)) { + guardedError(ReadScanError::CantReadImage); return QByteArray(); } return f.readAll(); }(); if (!content.isEmpty()) { - guardedCallback(std::move(content)); + processImage(std::move(content)); } } else if (!result.remoteContent.isEmpty()) { - guardedCallback(std::move(result.remoteContent)); + processImage(std::move(result.remoteContent)); } }; FileDialog::GetOpenPath( diff --git a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h index 4addb0db5..375c42785 100644 --- a/Telegram/SourceFiles/passport/passport_panel_edit_scans.h +++ b/Telegram/SourceFiles/passport/passport_panel_edit_scans.h @@ -30,6 +30,13 @@ class PanelController; class ScanButton; struct ScanInfo; +enum class ReadScanError { + FileTooLarge, + CantReadImage, + BadImageSize, + Unknown, +}; + class EditScans : public Ui::RpWidget { public: EditScans( @@ -44,7 +51,8 @@ public: static void ChooseScan( QPointer parent, - base::lambda callback); + base::lambda doneCallback, + base::lambda errorCallback); private: void setupContent(const QString &header);