mirror of https://github.com/procxx/kepka.git
				
				
				
			Secure files upload / download support.
This commit is contained in:
		
							parent
							
								
									f633ead3ab
								
							
						
					
					
						commit
						b2014f403e
					
				| 
						 | 
				
			
			@ -288,6 +288,7 @@ enum LocationType {
 | 
			
		|||
	DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation
 | 
			
		||||
	AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation
 | 
			
		||||
	VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation
 | 
			
		||||
	SecureFileLocation = 0xcbc7ee28, // mtpc_inputSecureFileLocation
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum FileStatus {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4339,6 +4339,45 @@ void HistoryWidget::uploadFile(
 | 
			
		|||
	Auth().api().sendFile(fileContent, type, options);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::subscribeToUploader() {
 | 
			
		||||
	if (_uploaderSubscriptions) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	using namespace Storage;
 | 
			
		||||
	Auth().uploader().photoReady(
 | 
			
		||||
	) | rpl::start_with_next([=](const UploadedPhoto &data) {
 | 
			
		||||
		photoUploaded(data.fullId, data.silent, data.file);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().photoProgress(
 | 
			
		||||
	) | rpl::start_with_next([=](const FullMsgId &fullId) {
 | 
			
		||||
		photoProgress(fullId);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().photoFailed(
 | 
			
		||||
	) | rpl::start_with_next([=](const FullMsgId &fullId) {
 | 
			
		||||
		photoFailed(fullId);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().documentReady(
 | 
			
		||||
	) | rpl::start_with_next([=](const UploadedDocument &data) {
 | 
			
		||||
		documentUploaded(data.fullId, data.silent, data.file);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().thumbDocumentReady(
 | 
			
		||||
	) | rpl::start_with_next([=](const UploadedThumbDocument &data) {
 | 
			
		||||
		thumbDocumentUploaded(
 | 
			
		||||
			data.fullId,
 | 
			
		||||
			data.silent,
 | 
			
		||||
			data.file,
 | 
			
		||||
			data.thumb);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().documentProgress(
 | 
			
		||||
	) | rpl::start_with_next([=](const FullMsgId &fullId) {
 | 
			
		||||
		documentProgress(fullId);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().documentFailed(
 | 
			
		||||
	) | rpl::start_with_next([=](const FullMsgId &fullId) {
 | 
			
		||||
		documentFailed(fullId);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::sendFileConfirmed(
 | 
			
		||||
		const std::shared_ptr<FileLoadResult> &file) {
 | 
			
		||||
	const auto channelId = peerToChannel(file->to.peer);
 | 
			
		||||
| 
						 | 
				
			
			@ -4358,13 +4397,7 @@ void HistoryWidget::sendFileConfirmed(
 | 
			
		|||
		it->msgId = newId;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onPhotoUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(documentReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(thumbDocumentReady(const FullMsgId&,bool,const MTPInputFile&,const MTPInputFile&)), this, SLOT(onThumbDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&, const MTPInputFile&)), Qt::UniqueConnection);
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(photoProgress(const FullMsgId&)), this, SLOT(onPhotoProgress(const FullMsgId&)), Qt::UniqueConnection);
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(documentProgress(const FullMsgId&)), this, SLOT(onDocumentProgress(const FullMsgId&)), Qt::UniqueConnection);
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(photoFailed(const FullMsgId&)), this, SLOT(onPhotoFailed(const FullMsgId&)), Qt::UniqueConnection);
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(documentFailed(const FullMsgId&)), this, SLOT(onDocumentFailed(const FullMsgId&)), Qt::UniqueConnection);
 | 
			
		||||
	subscribeToUploader();
 | 
			
		||||
 | 
			
		||||
	Auth().uploader().upload(newId, file);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4493,6 +4526,8 @@ void HistoryWidget::sendFileConfirmed(
 | 
			
		|||
				MTP_string(messagePostAuthor),
 | 
			
		||||
				MTP_long(groupId)),
 | 
			
		||||
			NewMessageUnread);
 | 
			
		||||
	} else {
 | 
			
		||||
		Unexpected("Type in sendFilesConfirmed.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Auth().data().sendHistoryChangeNotifications();
 | 
			
		||||
| 
						 | 
				
			
			@ -4502,21 +4537,21 @@ void HistoryWidget::sendFileConfirmed(
 | 
			
		|||
	App::main()->dialogsToUp();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::onPhotoUploaded(
 | 
			
		||||
void HistoryWidget::photoUploaded(
 | 
			
		||||
		const FullMsgId &newId,
 | 
			
		||||
		bool silent,
 | 
			
		||||
		const MTPInputFile &file) {
 | 
			
		||||
	Auth().api().sendUploadedPhoto(newId, file, silent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::onDocumentUploaded(
 | 
			
		||||
void HistoryWidget::documentUploaded(
 | 
			
		||||
		const FullMsgId &newId,
 | 
			
		||||
		bool silent,
 | 
			
		||||
		const MTPInputFile &file) {
 | 
			
		||||
	Auth().api().sendUploadedDocument(newId, file, base::none, silent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::onThumbDocumentUploaded(
 | 
			
		||||
void HistoryWidget::thumbDocumentUploaded(
 | 
			
		||||
		const FullMsgId &newId,
 | 
			
		||||
		bool silent,
 | 
			
		||||
		const MTPInputFile &file,
 | 
			
		||||
| 
						 | 
				
			
			@ -4524,7 +4559,7 @@ void HistoryWidget::onThumbDocumentUploaded(
 | 
			
		|||
	Auth().api().sendUploadedDocument(newId, file, thumb, silent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
 | 
			
		||||
void HistoryWidget::photoProgress(const FullMsgId &newId) {
 | 
			
		||||
	if (const auto item = App::histItemById(newId)) {
 | 
			
		||||
		const auto photo = item->media()
 | 
			
		||||
			? item->media()->photo()
 | 
			
		||||
| 
						 | 
				
			
			@ -4534,7 +4569,7 @@ void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::onDocumentProgress(const FullMsgId &newId) {
 | 
			
		||||
void HistoryWidget::documentProgress(const FullMsgId &newId) {
 | 
			
		||||
	if (const auto item = App::histItemById(newId)) {
 | 
			
		||||
		const auto media = item->media();
 | 
			
		||||
		const auto document = media ? media->document() : nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -4552,7 +4587,7 @@ void HistoryWidget::onDocumentProgress(const FullMsgId &newId) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::onPhotoFailed(const FullMsgId &newId) {
 | 
			
		||||
void HistoryWidget::photoFailed(const FullMsgId &newId) {
 | 
			
		||||
	if (const auto item = App::histItemById(newId)) {
 | 
			
		||||
		updateSendAction(
 | 
			
		||||
			item->history(),
 | 
			
		||||
| 
						 | 
				
			
			@ -4562,7 +4597,7 @@ void HistoryWidget::onPhotoFailed(const FullMsgId &newId) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HistoryWidget::onDocumentFailed(const FullMsgId &newId) {
 | 
			
		||||
void HistoryWidget::documentFailed(const FullMsgId &newId) {
 | 
			
		||||
	if (const auto item = App::histItemById(newId)) {
 | 
			
		||||
		const auto media = item->media();
 | 
			
		||||
		const auto document = media ? media->document() : nullptr;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,6 +68,9 @@ class TabbedSelector;
 | 
			
		|||
namespace Storage {
 | 
			
		||||
enum class MimeDataState;
 | 
			
		||||
struct PreparedList;
 | 
			
		||||
struct UploadedPhoto;
 | 
			
		||||
struct UploadedDocument;
 | 
			
		||||
struct UploadedThumbDocument;
 | 
			
		||||
} // namespace Storage
 | 
			
		||||
 | 
			
		||||
namespace HistoryView {
 | 
			
		||||
| 
						 | 
				
			
			@ -378,16 +381,6 @@ public slots:
 | 
			
		|||
	void onPinnedHide();
 | 
			
		||||
	void onFieldBarCancel();
 | 
			
		||||
 | 
			
		||||
	void onPhotoUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
 | 
			
		||||
	void onDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
 | 
			
		||||
	void onThumbDocumentUploaded(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb);
 | 
			
		||||
 | 
			
		||||
	void onPhotoProgress(const FullMsgId &msgId);
 | 
			
		||||
	void onDocumentProgress(const FullMsgId &msgId);
 | 
			
		||||
 | 
			
		||||
	void onPhotoFailed(const FullMsgId &msgId);
 | 
			
		||||
	void onDocumentFailed(const FullMsgId &msgId);
 | 
			
		||||
 | 
			
		||||
	void onReportSpamClicked();
 | 
			
		||||
	void onReportSpamHide();
 | 
			
		||||
	void onReportSpamClear();
 | 
			
		||||
| 
						 | 
				
			
			@ -519,6 +512,26 @@ private:
 | 
			
		|||
		MsgId replyTo,
 | 
			
		||||
		std::shared_ptr<SendingAlbum> album = nullptr);
 | 
			
		||||
 | 
			
		||||
	void subscribeToUploader();
 | 
			
		||||
 | 
			
		||||
	void photoUploaded(
 | 
			
		||||
		const FullMsgId &msgId,
 | 
			
		||||
		bool silent,
 | 
			
		||||
		const MTPInputFile &file);
 | 
			
		||||
	void photoProgress(const FullMsgId &msgId);
 | 
			
		||||
	void photoFailed(const FullMsgId &msgId);
 | 
			
		||||
	void documentUploaded(
 | 
			
		||||
		const FullMsgId &msgId,
 | 
			
		||||
		bool silent,
 | 
			
		||||
		const MTPInputFile &file);
 | 
			
		||||
	void thumbDocumentUploaded(
 | 
			
		||||
		const FullMsgId &msgId,
 | 
			
		||||
		bool silent,
 | 
			
		||||
		const MTPInputFile &file,
 | 
			
		||||
		const MTPInputFile &thumb);
 | 
			
		||||
	void documentProgress(const FullMsgId &msgId);
 | 
			
		||||
	void documentFailed(const FullMsgId &msgId);
 | 
			
		||||
 | 
			
		||||
	void itemRemoved(not_null<const HistoryItem*> item);
 | 
			
		||||
 | 
			
		||||
	// Updates position of controls around the message field,
 | 
			
		||||
| 
						 | 
				
			
			@ -816,6 +829,8 @@ private:
 | 
			
		|||
	int _recordingSamples = 0;
 | 
			
		||||
	int _recordCancelWidth;
 | 
			
		||||
 | 
			
		||||
	rpl::lifetime _uploaderSubscriptions;
 | 
			
		||||
 | 
			
		||||
	// This can animate for a very long time (like in music playing),
 | 
			
		||||
	// so it should be a BasicAnimation, not an Animation.
 | 
			
		||||
	BasicAnimation _a_recording;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -706,8 +706,8 @@ void Messenger::killDownloadSessions() {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Messenger::photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file) {
 | 
			
		||||
	if (!AuthSession::Exists()) return;
 | 
			
		||||
void Messenger::photoUpdated(const FullMsgId &msgId, const MTPInputFile &file) {
 | 
			
		||||
	Expects(AuthSession::Exists());
 | 
			
		||||
 | 
			
		||||
	auto i = photoUpdates.find(msgId);
 | 
			
		||||
	if (i != photoUpdates.end()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -770,6 +770,7 @@ void Messenger::authSessionCreate(UserId userId) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void Messenger::authSessionDestroy() {
 | 
			
		||||
	_uploaderSubscription = rpl::lifetime();
 | 
			
		||||
	_authSession.reset();
 | 
			
		||||
	_private->storedAuthSession.reset();
 | 
			
		||||
	_private->authSessionUserId = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -961,7 +962,12 @@ void Messenger::uploadProfilePhoto(QImage &&tosend, const PeerId &peerId) {
 | 
			
		|||
 | 
			
		||||
	SendMediaReady ready(SendMediaType::Photo, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, 0);
 | 
			
		||||
 | 
			
		||||
	connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&, bool, const MTPInputFile&)), this, SLOT(photoUpdated(const FullMsgId&, bool, const MTPInputFile&)), Qt::UniqueConnection);
 | 
			
		||||
	if (!_uploaderSubscription) {
 | 
			
		||||
		_uploaderSubscription = Auth().uploader().photoReady(
 | 
			
		||||
		) | rpl::start_with_next([=](const Storage::UploadedPhoto &data) {
 | 
			
		||||
			photoUpdated(data.fullId, data.file);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	FullMsgId newId(peerToChannel(peerId), clientMsgId());
 | 
			
		||||
	regPhotoUpdate(peerId, newId);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -199,8 +199,6 @@ signals:
 | 
			
		|||
public slots:
 | 
			
		||||
	void onAllKeysDestroyed();
 | 
			
		||||
 | 
			
		||||
	void photoUpdated(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
 | 
			
		||||
 | 
			
		||||
	void onSwitchDebugMode();
 | 
			
		||||
	void onSwitchWorkMode();
 | 
			
		||||
	void onSwitchTestMode();
 | 
			
		||||
| 
						 | 
				
			
			@ -216,6 +214,7 @@ private:
 | 
			
		|||
	static void QuitAttempt();
 | 
			
		||||
	void quitDelayed();
 | 
			
		||||
 | 
			
		||||
	void photoUpdated(const FullMsgId &msgId, const MTPInputFile &file);
 | 
			
		||||
	void loggedOut();
 | 
			
		||||
 | 
			
		||||
	not_null<Core::Launcher*> _launcher;
 | 
			
		||||
| 
						 | 
				
			
			@ -244,6 +243,9 @@ private:
 | 
			
		|||
	base::Observable<void> _passcodedChanged;
 | 
			
		||||
	QPointer<BoxContent> _badProxyDisableBox;
 | 
			
		||||
 | 
			
		||||
	// While profile photo uploading is not moved to apiwrap.
 | 
			
		||||
	rpl::lifetime _uploaderSubscription;
 | 
			
		||||
 | 
			
		||||
	std::unique_ptr<Media::Audio::Instance> _audio;
 | 
			
		||||
	QImage _logo;
 | 
			
		||||
	QImage _logoNoMargin;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,20 +9,136 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
 | 
			
		||||
#include "passport/passport_form_controller.h"
 | 
			
		||||
#include "ui/widgets/input_fields.h"
 | 
			
		||||
#include "ui/widgets/buttons.h"
 | 
			
		||||
#include "ui/text_options.h"
 | 
			
		||||
#include "lang/lang_keys.h"
 | 
			
		||||
#include "core/file_utilities.h"
 | 
			
		||||
#include "styles/style_widgets.h"
 | 
			
		||||
#include "styles/style_boxes.h"
 | 
			
		||||
#include "styles/style_passport.h"
 | 
			
		||||
 | 
			
		||||
namespace Passport {
 | 
			
		||||
 | 
			
		||||
class ScanButton : public Ui::RippleButton {
 | 
			
		||||
public:
 | 
			
		||||
	ScanButton(
 | 
			
		||||
		QWidget *parent,
 | 
			
		||||
		const QString &title,
 | 
			
		||||
		const QString &description);
 | 
			
		||||
 | 
			
		||||
	void setImage(const QImage &image);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	int resizeGetHeight(int newWidth) override;
 | 
			
		||||
 | 
			
		||||
	void paintEvent(QPaintEvent *e) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	int countAvailableWidth() const;
 | 
			
		||||
	int countAvailableWidth(int newWidth) const;
 | 
			
		||||
 | 
			
		||||
	Text _title;
 | 
			
		||||
	Text _description;
 | 
			
		||||
	int _titleHeight = 0;
 | 
			
		||||
	int _descriptionHeight = 0;
 | 
			
		||||
	QImage _image;
 | 
			
		||||
	object_ptr<Ui::IconButton> _delete = { nullptr };
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ScanButton::ScanButton(
 | 
			
		||||
	QWidget *parent,
 | 
			
		||||
	const QString &title,
 | 
			
		||||
	const QString &description)
 | 
			
		||||
: RippleButton(parent, st::passportRowRipple)
 | 
			
		||||
, _title(
 | 
			
		||||
	st::semiboldTextStyle,
 | 
			
		||||
	title,
 | 
			
		||||
	Ui::NameTextOptions(),
 | 
			
		||||
	st::boxWideWidth / 2)
 | 
			
		||||
, _description(
 | 
			
		||||
	st::defaultTextStyle,
 | 
			
		||||
	description,
 | 
			
		||||
	Ui::NameTextOptions(),
 | 
			
		||||
	st::boxWideWidth / 2)
 | 
			
		||||
, _delete(this, st::passportRowCheckbox) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScanButton::setImage(const QImage &image) {
 | 
			
		||||
	_image = image;
 | 
			
		||||
	update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ScanButton::resizeGetHeight(int newWidth) {
 | 
			
		||||
	const auto availableWidth = countAvailableWidth(newWidth);
 | 
			
		||||
	_titleHeight = _title.countHeight(availableWidth);
 | 
			
		||||
	_descriptionHeight = _description.countHeight(availableWidth);
 | 
			
		||||
	const auto result = st::passportRowPadding.top()
 | 
			
		||||
		+ _titleHeight
 | 
			
		||||
		+ st::passportRowSkip
 | 
			
		||||
		+ _descriptionHeight
 | 
			
		||||
		+ st::passportRowPadding.bottom();
 | 
			
		||||
	const auto right = st::passportRowPadding.right();
 | 
			
		||||
	_delete->moveToRight(
 | 
			
		||||
		right,
 | 
			
		||||
		(result - _delete->height()) / 2,
 | 
			
		||||
		newWidth);
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ScanButton::countAvailableWidth(int newWidth) const {
 | 
			
		||||
	return newWidth
 | 
			
		||||
		- st::passportRowPadding.left()
 | 
			
		||||
		- st::passportRowPadding.right()
 | 
			
		||||
		- _delete->width();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ScanButton::countAvailableWidth() const {
 | 
			
		||||
	return countAvailableWidth(width());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ScanButton::paintEvent(QPaintEvent *e) {
 | 
			
		||||
	Painter p(this);
 | 
			
		||||
 | 
			
		||||
	const auto ms = getms();
 | 
			
		||||
	paintRipple(p, 0, 0, ms);
 | 
			
		||||
 | 
			
		||||
	auto left = st::passportRowPadding.left();
 | 
			
		||||
	auto availableWidth = countAvailableWidth();
 | 
			
		||||
	auto top = st::passportRowPadding.top();
 | 
			
		||||
	const auto size = height() - top - st::passportRowPadding.bottom();
 | 
			
		||||
	if (_image.isNull()) {
 | 
			
		||||
		p.fillRect(left, top, size, size, Qt::black);
 | 
			
		||||
	} else {
 | 
			
		||||
		PainterHighQualityEnabler hq(p);
 | 
			
		||||
		if (_image.width() > _image.height()) {
 | 
			
		||||
			auto newheight = size * _image.height() / _image.width();
 | 
			
		||||
			p.drawImage(QRect(left, top + (size - newheight) / 2, size, newheight), _image);
 | 
			
		||||
		} else {
 | 
			
		||||
			auto newwidth = size * _image.width() / _image.height();
 | 
			
		||||
			p.drawImage(QRect(left + (size - newwidth) / 2, top, newwidth, size), _image);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	left += size + st::passportRowPadding.left();
 | 
			
		||||
	availableWidth -= size + st::passportRowPadding.left();
 | 
			
		||||
 | 
			
		||||
	_title.drawLeft(p, left, top, availableWidth, width());
 | 
			
		||||
	top += _titleHeight + st::passportRowSkip;
 | 
			
		||||
 | 
			
		||||
	_description.drawLeft(p, left, top, availableWidth, width());
 | 
			
		||||
	top += _descriptionHeight + st::passportRowPadding.bottom();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
IdentityBox::IdentityBox(
 | 
			
		||||
	QWidget*,
 | 
			
		||||
	not_null<FormController*> controller,
 | 
			
		||||
	int fieldIndex,
 | 
			
		||||
	const IdentityData &data)
 | 
			
		||||
	const IdentityData &data,
 | 
			
		||||
	std::vector<ScanInfo> &&files)
 | 
			
		||||
: _controller(controller)
 | 
			
		||||
, _fieldIndex(fieldIndex)
 | 
			
		||||
, _files(std::move(files))
 | 
			
		||||
, _uploadScan(this, "Upload scans") // #TODO langs
 | 
			
		||||
, _name(
 | 
			
		||||
	this,
 | 
			
		||||
	st::defaultInputField,
 | 
			
		||||
| 
						 | 
				
			
			@ -38,21 +154,48 @@ IdentityBox::IdentityBox(
 | 
			
		|||
void IdentityBox::prepare() {
 | 
			
		||||
	setTitle(langFactory(lng_passport_identity_title));
 | 
			
		||||
 | 
			
		||||
	auto index = 0;
 | 
			
		||||
	auto height = st::contactPadding.top();
 | 
			
		||||
	for (const auto &scan : _files) {
 | 
			
		||||
		_scans.push_back(object_ptr<ScanButton>(this, QString("Scan %1").arg(++index), scan.date));
 | 
			
		||||
		_scans.back()->setImage(scan.thumb);
 | 
			
		||||
		_scans.back()->resizeToWidth(st::boxWideWidth);
 | 
			
		||||
		height += _scans.back()->height();
 | 
			
		||||
	}
 | 
			
		||||
	height += st::contactPadding.top()
 | 
			
		||||
		+ _uploadScan->height()
 | 
			
		||||
		+ st::contactSkip
 | 
			
		||||
		+ _name->height()
 | 
			
		||||
		+ st::contactSkip
 | 
			
		||||
		+ _surname->height()
 | 
			
		||||
		+ st::contactPadding.bottom()
 | 
			
		||||
		+ st::boxPadding.bottom();
 | 
			
		||||
 | 
			
		||||
	addButton(langFactory(lng_settings_save), [=] {
 | 
			
		||||
		save();
 | 
			
		||||
	});
 | 
			
		||||
	addButton(langFactory(lng_cancel), [=] {
 | 
			
		||||
		closeBox();
 | 
			
		||||
	});
 | 
			
		||||
	_controller->scanUpdated(
 | 
			
		||||
	) | rpl::start_with_next([=](ScanInfo &&info) {
 | 
			
		||||
		updateScan(std::move(info));
 | 
			
		||||
	}, lifetime());
 | 
			
		||||
 | 
			
		||||
	setDimensions(
 | 
			
		||||
		st::boxWideWidth,
 | 
			
		||||
		(st::contactPadding.top()
 | 
			
		||||
			+ _name->height()
 | 
			
		||||
			+ st::contactSkip
 | 
			
		||||
			+ _surname->height()
 | 
			
		||||
			+ st::contactPadding.bottom()
 | 
			
		||||
			+ st::boxPadding.bottom()));
 | 
			
		||||
	_uploadScan->addClickHandler([=] {
 | 
			
		||||
		chooseScan();
 | 
			
		||||
	});
 | 
			
		||||
	setDimensions(st::boxWideWidth, height);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IdentityBox::updateScan(ScanInfo &&info) {
 | 
			
		||||
	const auto i = ranges::find(_files, info.key, [](const ScanInfo &file) {
 | 
			
		||||
		return file.key;
 | 
			
		||||
	});
 | 
			
		||||
	if (i != _files.end()) {
 | 
			
		||||
		*i = info;
 | 
			
		||||
		_scans[i - _files.begin()]->setImage(i->thumb);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IdentityBox::setInnerFocus() {
 | 
			
		||||
| 
						 | 
				
			
			@ -67,12 +210,51 @@ void IdentityBox::resizeEvent(QResizeEvent *e) {
 | 
			
		|||
			- st::boxPadding.right()),
 | 
			
		||||
		_name->height());
 | 
			
		||||
	_surname->resize(_name->width(), _surname->height());
 | 
			
		||||
	_name->moveToLeft(
 | 
			
		||||
		st::boxPadding.left(),
 | 
			
		||||
		st::contactPadding.top());
 | 
			
		||||
	_surname->moveToLeft(
 | 
			
		||||
		st::boxPadding.left(),
 | 
			
		||||
		_name->y() + _name->height() + st::contactSkip);
 | 
			
		||||
 | 
			
		||||
	auto top = st::contactPadding.top();
 | 
			
		||||
	for (const auto &scan : _scans) {
 | 
			
		||||
		scan->moveToLeft(0, top);
 | 
			
		||||
		top += scan->height();
 | 
			
		||||
	}
 | 
			
		||||
	top += st::contactPadding.top();
 | 
			
		||||
	_uploadScan->moveToLeft(st::boxPadding.left(), top);
 | 
			
		||||
	top += _uploadScan->height() + st::contactSkip;
 | 
			
		||||
	_name->moveToLeft(st::boxPadding.left(), top);
 | 
			
		||||
	top += _name->height() + st::contactSkip;
 | 
			
		||||
	_surname->moveToLeft(st::boxPadding.left(), top);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IdentityBox::chooseScan() {
 | 
			
		||||
	const auto filter = FileDialog::AllFilesFilter()
 | 
			
		||||
		+ qsl(";;Image files (*")
 | 
			
		||||
		+ cImgExtensions().join(qsl(" *"))
 | 
			
		||||
		+ qsl(")");
 | 
			
		||||
	const auto callback = [=](FileDialog::OpenResult &&result) {
 | 
			
		||||
		if (result.paths.size() == 1) {
 | 
			
		||||
			encryptScan(result.paths.front());
 | 
			
		||||
		} else if (!result.remoteContent.isEmpty()) {
 | 
			
		||||
			encryptScanContent(std::move(result.remoteContent));
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	FileDialog::GetOpenPath(
 | 
			
		||||
		"Choose scan image",
 | 
			
		||||
		filter,
 | 
			
		||||
		base::lambda_guarded(this, callback));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IdentityBox::encryptScan(const QString &path) {
 | 
			
		||||
	encryptScanContent([&] {
 | 
			
		||||
		QFile f(path);
 | 
			
		||||
		if (!f.open(QIODevice::ReadOnly)) {
 | 
			
		||||
			return QByteArray();
 | 
			
		||||
		}
 | 
			
		||||
		return f.readAll();
 | 
			
		||||
	}());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IdentityBox::encryptScanContent(QByteArray &&content) {
 | 
			
		||||
	_uploadScan->hide();
 | 
			
		||||
	_controller->uploadScan(_fieldIndex, std::move(content));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void IdentityBox::save() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,12 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
#include "boxes/abstract_box.h"
 | 
			
		||||
 | 
			
		||||
namespace Ui {
 | 
			
		||||
class LinkButton;
 | 
			
		||||
class InputField;
 | 
			
		||||
} // namespace Ui
 | 
			
		||||
 | 
			
		||||
namespace Passport {
 | 
			
		||||
 | 
			
		||||
class FormController;
 | 
			
		||||
struct ScanInfo;
 | 
			
		||||
class ScanButton;
 | 
			
		||||
 | 
			
		||||
struct IdentityData {
 | 
			
		||||
	QString name;
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +31,8 @@ public:
 | 
			
		|||
		QWidget*,
 | 
			
		||||
		not_null<FormController*> controller,
 | 
			
		||||
		int fieldIndex,
 | 
			
		||||
		const IdentityData &data);
 | 
			
		||||
		const IdentityData &data,
 | 
			
		||||
		std::vector<ScanInfo> &&files);
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	void prepare() override;
 | 
			
		||||
| 
						 | 
				
			
			@ -37,11 +41,19 @@ protected:
 | 
			
		|||
	void resizeEvent(QResizeEvent *e) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void chooseScan();
 | 
			
		||||
	void encryptScan(const QString &path);
 | 
			
		||||
	void encryptScanContent(QByteArray &&content);
 | 
			
		||||
	void updateScan(ScanInfo &&info);
 | 
			
		||||
	void save();
 | 
			
		||||
 | 
			
		||||
	not_null<FormController*> _controller;
 | 
			
		||||
	int _fieldIndex = -1;
 | 
			
		||||
 | 
			
		||||
	std::vector<ScanInfo> _files;
 | 
			
		||||
 | 
			
		||||
	std::vector<object_ptr<ScanButton>> _scans;
 | 
			
		||||
	object_ptr<Ui::LinkButton> _uploadScan;
 | 
			
		||||
	object_ptr<Ui::InputField> _name;
 | 
			
		||||
	object_ptr<Ui::InputField> _surname;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,74 +22,6 @@ constexpr auto kMinPadding = 32;
 | 
			
		|||
constexpr auto kMaxPadding = 255;
 | 
			
		||||
constexpr auto kAlignTo = 16;
 | 
			
		||||
 | 
			
		||||
base::byte_vector SerializeData(const std::map<QString, QString> &data) {
 | 
			
		||||
	auto root = QJsonObject();
 | 
			
		||||
	for (const auto &[key, value] : data) {
 | 
			
		||||
		root.insert(key, value);
 | 
			
		||||
	}
 | 
			
		||||
	auto document = QJsonDocument(root);
 | 
			
		||||
	const auto result = document.toJson(QJsonDocument::Compact);
 | 
			
		||||
	const auto bytes = gsl::as_bytes(gsl::make_span(result));
 | 
			
		||||
	return { bytes.begin(), bytes.end() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::map<QString, QString> DeserializeData(base::const_byte_span bytes) {
 | 
			
		||||
	const auto serialized = QByteArray::fromRawData(
 | 
			
		||||
		reinterpret_cast<const char*>(bytes.data()),
 | 
			
		||||
		bytes.size());
 | 
			
		||||
	auto error = QJsonParseError();
 | 
			
		||||
	auto document = QJsonDocument::fromJson(serialized, &error);
 | 
			
		||||
	if (error.error != QJsonParseError::NoError) {
 | 
			
		||||
		LOG(("API Error: Could not deserialize decrypted JSON, error %1"
 | 
			
		||||
			).arg(error.errorString()));
 | 
			
		||||
		return {};
 | 
			
		||||
	} else if (!document.isObject()) {
 | 
			
		||||
		LOG(("API Error: decrypted JSON root is not an object."));
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	auto object = document.object();
 | 
			
		||||
	auto result = std::map<QString, QString>();
 | 
			
		||||
	for (auto i = object.constBegin(), e = object.constEnd(); i != e; ++i) {
 | 
			
		||||
		const auto key = i.key();
 | 
			
		||||
		switch (i->type()) {
 | 
			
		||||
		case QJsonValue::Null: {
 | 
			
		||||
			LOG(("API Error: null found inside decrypted JSON root. "
 | 
			
		||||
				"Defaulting to empty string value."));
 | 
			
		||||
			result[key] = QString();
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Undefined: {
 | 
			
		||||
			LOG(("API Error: undefined found inside decrypted JSON root. "
 | 
			
		||||
				"Defaulting to empty string value."));
 | 
			
		||||
			result[key] = QString();
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Bool: {
 | 
			
		||||
			LOG(("API Error: bool found inside decrypted JSON root. "
 | 
			
		||||
				"Aborting."));
 | 
			
		||||
			return {};
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Double: {
 | 
			
		||||
			LOG(("API Error: double found inside decrypted JSON root. "
 | 
			
		||||
				"Converting to string."));
 | 
			
		||||
			result[key] = QString::number(i->toDouble());
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::String: {
 | 
			
		||||
			result[key] = i->toString();
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Array: {
 | 
			
		||||
			LOG(("API Error: array found inside decrypted JSON root. "
 | 
			
		||||
				"Aborting."));
 | 
			
		||||
			return {};
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Object: {
 | 
			
		||||
			LOG(("API Error: object found inside decrypted JSON root. "
 | 
			
		||||
				"Aborting."));
 | 
			
		||||
			return {};
 | 
			
		||||
		} break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace
 | 
			
		||||
 | 
			
		||||
struct AesParams {
 | 
			
		||||
| 
						 | 
				
			
			@ -162,9 +94,8 @@ base::byte_vector PasswordHashForSecret(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
bool CheckBytesMod255(base::const_byte_span bytes) {
 | 
			
		||||
	const auto full = std::accumulate(
 | 
			
		||||
		bytes.begin(),
 | 
			
		||||
		bytes.end(),
 | 
			
		||||
	const auto full = ranges::accumulate(
 | 
			
		||||
		bytes,
 | 
			
		||||
		0ULL,
 | 
			
		||||
		[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
 | 
			
		||||
	const auto mod = (full % 255ULL);
 | 
			
		||||
| 
						 | 
				
			
			@ -178,9 +109,8 @@ bool CheckSecretBytes(base::const_byte_span secret) {
 | 
			
		|||
base::byte_vector GenerateSecretBytes() {
 | 
			
		||||
	auto result = base::byte_vector(kSecretSize);
 | 
			
		||||
	memset_rand(result.data(), result.size());
 | 
			
		||||
	const auto full = std::accumulate(
 | 
			
		||||
		result.begin(),
 | 
			
		||||
		result.end(),
 | 
			
		||||
	const auto full = ranges::accumulate(
 | 
			
		||||
		result,
 | 
			
		||||
		0ULL,
 | 
			
		||||
		[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
 | 
			
		||||
	const auto mod = (full % 255ULL);
 | 
			
		||||
| 
						 | 
				
			
			@ -228,13 +158,81 @@ base::byte_vector Concatenate(
 | 
			
		|||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
base::byte_vector SerializeData(const std::map<QString, QString> &data) {
 | 
			
		||||
	auto root = QJsonObject();
 | 
			
		||||
	for (const auto &[key, value] : data) {
 | 
			
		||||
		root.insert(key, value);
 | 
			
		||||
	}
 | 
			
		||||
	auto document = QJsonDocument(root);
 | 
			
		||||
	const auto result = document.toJson(QJsonDocument::Compact);
 | 
			
		||||
	const auto bytes = gsl::as_bytes(gsl::make_span(result));
 | 
			
		||||
	return { bytes.begin(), bytes.end() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::map<QString, QString> DeserializeData(base::const_byte_span bytes) {
 | 
			
		||||
	const auto serialized = QByteArray::fromRawData(
 | 
			
		||||
		reinterpret_cast<const char*>(bytes.data()),
 | 
			
		||||
		bytes.size());
 | 
			
		||||
	auto error = QJsonParseError();
 | 
			
		||||
	auto document = QJsonDocument::fromJson(serialized, &error);
 | 
			
		||||
	if (error.error != QJsonParseError::NoError) {
 | 
			
		||||
		LOG(("API Error: Could not deserialize decrypted JSON, error %1"
 | 
			
		||||
			).arg(error.errorString()));
 | 
			
		||||
		return {};
 | 
			
		||||
	} else if (!document.isObject()) {
 | 
			
		||||
		LOG(("API Error: decrypted JSON root is not an object."));
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	auto object = document.object();
 | 
			
		||||
	auto result = std::map<QString, QString>();
 | 
			
		||||
	for (auto i = object.constBegin(), e = object.constEnd(); i != e; ++i) {
 | 
			
		||||
		const auto key = i.key();
 | 
			
		||||
		switch (i->type()) {
 | 
			
		||||
		case QJsonValue::Null: {
 | 
			
		||||
			LOG(("API Error: null found inside decrypted JSON root. "
 | 
			
		||||
				"Defaulting to empty string value."));
 | 
			
		||||
			result[key] = QString();
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Undefined: {
 | 
			
		||||
			LOG(("API Error: undefined found inside decrypted JSON root. "
 | 
			
		||||
				"Defaulting to empty string value."));
 | 
			
		||||
			result[key] = QString();
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Bool: {
 | 
			
		||||
			LOG(("API Error: bool found inside decrypted JSON root. "
 | 
			
		||||
				"Aborting."));
 | 
			
		||||
			return {};
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Double: {
 | 
			
		||||
			LOG(("API Error: double found inside decrypted JSON root. "
 | 
			
		||||
				"Converting to string."));
 | 
			
		||||
			result[key] = QString::number(i->toDouble());
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::String: {
 | 
			
		||||
			result[key] = i->toString();
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Array: {
 | 
			
		||||
			LOG(("API Error: array found inside decrypted JSON root. "
 | 
			
		||||
				"Aborting."));
 | 
			
		||||
			return {};
 | 
			
		||||
		} break;
 | 
			
		||||
		case QJsonValue::Object: {
 | 
			
		||||
			LOG(("API Error: object found inside decrypted JSON root. "
 | 
			
		||||
				"Aborting."));
 | 
			
		||||
			return {};
 | 
			
		||||
		} break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EncryptedData EncryptData(base::const_byte_span bytes) {
 | 
			
		||||
	return EncryptData(bytes, GenerateSecretBytes());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EncryptedData EncryptData(
 | 
			
		||||
		base::const_byte_span dataSecret,
 | 
			
		||||
		const std::map<QString, QString> &data) {
 | 
			
		||||
	Expects(dataSecret.size() == kSecretSize);
 | 
			
		||||
 | 
			
		||||
	const auto bytes = SerializeData(data);
 | 
			
		||||
 | 
			
		||||
		base::const_byte_span bytes,
 | 
			
		||||
		base::const_byte_span dataSecret) {
 | 
			
		||||
	constexpr auto kFromPadding = kMinPadding + kAlignTo - 1;
 | 
			
		||||
	constexpr auto kPaddingDelta = kMaxPadding - kFromPadding;
 | 
			
		||||
	const auto randomPadding = kFromPadding
 | 
			
		||||
| 
						 | 
				
			
			@ -254,14 +252,16 @@ EncryptedData EncryptData(
 | 
			
		|||
	const auto dataHash = openssl::Sha256(unencrypted);
 | 
			
		||||
	const auto dataSecretHash = openssl::Sha512(
 | 
			
		||||
		Concatenate(dataSecret, dataHash));
 | 
			
		||||
 | 
			
		||||
	auto params = PrepareAesParams(dataSecretHash);
 | 
			
		||||
	return {
 | 
			
		||||
		{ dataSecret.begin(), dataSecret.end() },
 | 
			
		||||
		{ dataHash.begin(), dataHash.end() },
 | 
			
		||||
		Encrypt(unencrypted, std::move(params))
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::map<QString, QString> DecryptData(
 | 
			
		||||
base::byte_vector DecryptData(
 | 
			
		||||
		base::const_byte_span encrypted,
 | 
			
		||||
		base::const_byte_span dataHash,
 | 
			
		||||
		base::const_byte_span dataSecret) {
 | 
			
		||||
| 
						 | 
				
			
			@ -292,7 +292,7 @@ std::map<QString, QString> DecryptData(
 | 
			
		|||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	const auto bytes = gsl::make_span(decrypted).subspan(padding);
 | 
			
		||||
	return DeserializeData(bytes);
 | 
			
		||||
	return { bytes.begin(), bytes.end() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
base::byte_vector PrepareValueHash(
 | 
			
		||||
| 
						 | 
				
			
			@ -302,6 +302,20 @@ base::byte_vector PrepareValueHash(
 | 
			
		|||
	return { result.begin(), result.end() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
base::byte_vector PrepareFilesHash(
 | 
			
		||||
		gsl::span<base::const_byte_span> fileHashes,
 | 
			
		||||
		base::const_byte_span valueSecret) {
 | 
			
		||||
	auto resultInner = base::byte_vector{
 | 
			
		||||
		valueSecret.begin(),
 | 
			
		||||
		valueSecret.end()
 | 
			
		||||
	};
 | 
			
		||||
	for (const auto &hash : base::reversed(fileHashes)) {
 | 
			
		||||
		resultInner = Concatenate(hash, resultInner);
 | 
			
		||||
	}
 | 
			
		||||
	const auto result = openssl::Sha256(resultInner);
 | 
			
		||||
	return { result.begin(), result.end() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
base::byte_vector EncryptValueSecret(
 | 
			
		||||
		base::const_byte_span valueSecret,
 | 
			
		||||
		base::const_byte_span secret,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,16 +20,22 @@ base::byte_vector DecryptSecretBytes(
 | 
			
		|||
 | 
			
		||||
base::byte_vector PasswordHashForSecret(base::const_byte_span passwordUtf8);
 | 
			
		||||
 | 
			
		||||
base::byte_vector SerializeData(const std::map<QString, QString> &data);
 | 
			
		||||
std::map<QString, QString> DeserializeData(base::const_byte_span bytes);
 | 
			
		||||
 | 
			
		||||
struct EncryptedData {
 | 
			
		||||
	base::byte_vector secret;
 | 
			
		||||
	base::byte_vector hash;
 | 
			
		||||
	base::byte_vector bytes;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
EncryptedData EncryptData(
 | 
			
		||||
	base::const_byte_span dataSecret,
 | 
			
		||||
	const std::map<QString, QString> &data);
 | 
			
		||||
EncryptedData EncryptData(base::const_byte_span bytes);
 | 
			
		||||
 | 
			
		||||
std::map<QString, QString> DecryptData(
 | 
			
		||||
EncryptedData EncryptData(
 | 
			
		||||
	base::const_byte_span bytes,
 | 
			
		||||
	base::const_byte_span dataSecret);
 | 
			
		||||
 | 
			
		||||
base::byte_vector DecryptData(
 | 
			
		||||
	base::const_byte_span encrypted,
 | 
			
		||||
	base::const_byte_span dataHash,
 | 
			
		||||
	base::const_byte_span dataSecret);
 | 
			
		||||
| 
						 | 
				
			
			@ -48,4 +54,8 @@ base::byte_vector DecryptValueSecret(
 | 
			
		|||
	base::const_byte_span secret,
 | 
			
		||||
	base::const_byte_span valueHash);
 | 
			
		||||
 | 
			
		||||
base::byte_vector PrepareFilesHash(
 | 
			
		||||
	gsl::span<base::const_byte_span> fileHashes,
 | 
			
		||||
	base::const_byte_span valueSecret);
 | 
			
		||||
 | 
			
		||||
} // namespace Passport
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
#include "lang/lang_keys.h"
 | 
			
		||||
#include "base/openssl_help.h"
 | 
			
		||||
#include "mainwindow.h"
 | 
			
		||||
#include "auth_session.h"
 | 
			
		||||
#include "storage/localimageloader.h"
 | 
			
		||||
#include "storage/file_upload.h"
 | 
			
		||||
#include "storage/file_download.h"
 | 
			
		||||
 | 
			
		||||
namespace Passport {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,10 +32,33 @@ FormRequest::FormRequest(
 | 
			
		|||
, publicKey(publicKey) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FormController::UploadedScan::~UploadedScan() {
 | 
			
		||||
	if (fullId) {
 | 
			
		||||
		Auth().uploader().cancel(fullId);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FormController::EditFile::EditFile(
 | 
			
		||||
	const File &fields,
 | 
			
		||||
	std::unique_ptr<UploadedScan> &&uploaded)
 | 
			
		||||
: fields(std::move(fields))
 | 
			
		||||
, uploaded(std::move(uploaded)) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FormController::Field::Field(Type type) : type(type) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename FileHashes>
 | 
			
		||||
base::byte_vector FormController::computeFilesHash(
 | 
			
		||||
		FileHashes fileHashes,
 | 
			
		||||
		base::const_byte_span valueSecret) {
 | 
			
		||||
	auto hashesVector = std::vector<base::const_byte_span>();
 | 
			
		||||
	for (const auto &hash : fileHashes) {
 | 
			
		||||
		hashesVector.push_back(gsl::as_bytes(gsl::make_span(hash)));
 | 
			
		||||
	}
 | 
			
		||||
	return PrepareFilesHash(hashesVector, valueSecret);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FormController::FormController(
 | 
			
		||||
	not_null<Window::Controller*> controller,
 | 
			
		||||
	const FormRequest &request)
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +101,20 @@ void FormController::submitPassword(const QString &password) {
 | 
			
		|||
			_passwordHashForSecret);
 | 
			
		||||
		for (auto &field : _form.fields) {
 | 
			
		||||
			field.data.values = fillData(field.data);
 | 
			
		||||
			if (auto &document = field.document) {
 | 
			
		||||
				const auto filesHash = gsl::as_bytes(gsl::make_span(document->filesHash));
 | 
			
		||||
				document->filesSecret = DecryptValueSecret(
 | 
			
		||||
					gsl::as_bytes(gsl::make_span(document->filesSecretEncrypted)),
 | 
			
		||||
					_secret,
 | 
			
		||||
					filesHash);
 | 
			
		||||
				if (document->filesSecret.empty()
 | 
			
		||||
					&& !document->files.empty()) {
 | 
			
		||||
					LOG(("API Error: Empty decrypted files secret. "
 | 
			
		||||
						"Forgetting files."));
 | 
			
		||||
					document->files.clear();
 | 
			
		||||
					document->filesHash.clear();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		_secretReady.fire({});
 | 
			
		||||
	}).fail([=](const RPCError &error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +137,101 @@ QString FormController::passwordHint() const {
 | 
			
		|||
	return _password.hint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::uploadScan(int index, QByteArray &&content) {
 | 
			
		||||
	Expects(_editBox != nullptr);
 | 
			
		||||
	Expects(index >= 0 && index < _form.fields.size());
 | 
			
		||||
	Expects(_form.fields[index].document.has_value());
 | 
			
		||||
 | 
			
		||||
	auto &document = *_form.fields[index].document;
 | 
			
		||||
	if (document.filesSecret.empty()) {
 | 
			
		||||
		document.filesSecret = GenerateSecretBytes();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto weak = _editBox;
 | 
			
		||||
	crl::async([
 | 
			
		||||
		=,
 | 
			
		||||
		bytes = std::move(content),
 | 
			
		||||
		filesSecret = document.filesSecret
 | 
			
		||||
	] {
 | 
			
		||||
		auto data = EncryptData(
 | 
			
		||||
			gsl::as_bytes(gsl::make_span(bytes)),
 | 
			
		||||
			filesSecret);
 | 
			
		||||
		auto result = UploadedScan();
 | 
			
		||||
		result.fileId = rand_value<uint64>();
 | 
			
		||||
		result.hash = std::move(data.hash);
 | 
			
		||||
		result.bytes = std::move(data.bytes);
 | 
			
		||||
		result.md5checksum.resize(32);
 | 
			
		||||
		hashMd5Hex(
 | 
			
		||||
			result.bytes.data(),
 | 
			
		||||
			result.bytes.size(),
 | 
			
		||||
			result.md5checksum.data());
 | 
			
		||||
		crl::on_main([=, encrypted = std::move(result)]() mutable {
 | 
			
		||||
			if (weak) {
 | 
			
		||||
				uploadEncryptedScan(index, std::move(encrypted));
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::subscribeToUploader() {
 | 
			
		||||
	if (_uploaderSubscriptions) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	Auth().uploader().secureReady(
 | 
			
		||||
	) | rpl::start_with_next([=](const Storage::UploadedSecure &data) {
 | 
			
		||||
		scanUploaded(data);
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().secureProgress(
 | 
			
		||||
	) | rpl::start_with_next([=](const FullMsgId &fullId) {
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
	Auth().uploader().secureFailed(
 | 
			
		||||
	) | rpl::start_with_next([=](const FullMsgId &fullId) {
 | 
			
		||||
	}, _uploaderSubscriptions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::uploadEncryptedScan(int index, UploadedScan &&data) {
 | 
			
		||||
	Expects(_editBox != nullptr);
 | 
			
		||||
	Expects(index >= 0 && index < _form.fields.size());
 | 
			
		||||
	Expects(_form.fields[index].document.has_value());
 | 
			
		||||
 | 
			
		||||
	subscribeToUploader();
 | 
			
		||||
 | 
			
		||||
	_form.fields[index].document->filesInEdit.emplace_back(
 | 
			
		||||
		File(),
 | 
			
		||||
		std::make_unique<UploadedScan>(std::move(data)));
 | 
			
		||||
	auto &file = _form.fields[index].document->filesInEdit.back();
 | 
			
		||||
 | 
			
		||||
	auto uploaded = std::make_shared<FileLoadResult>(
 | 
			
		||||
		TaskId(),
 | 
			
		||||
		file.uploaded->fileId,
 | 
			
		||||
		FileLoadTo(PeerId(0), false, MsgId(0)),
 | 
			
		||||
		TextWithTags(),
 | 
			
		||||
		std::shared_ptr<SendingAlbum>(nullptr));
 | 
			
		||||
	uploaded->type = SendMediaType::Secure;
 | 
			
		||||
	uploaded->content = QByteArray::fromRawData(
 | 
			
		||||
		reinterpret_cast<char*>(file.uploaded->bytes.data()),
 | 
			
		||||
		file.uploaded->bytes.size());
 | 
			
		||||
	uploaded->setFileData(uploaded->content);
 | 
			
		||||
	uploaded->filemd5 = file.uploaded->md5checksum;
 | 
			
		||||
 | 
			
		||||
	file.uploaded->fullId = FullMsgId(0, clientMsgId());
 | 
			
		||||
	Auth().uploader().upload(file.uploaded->fullId, std::move(uploaded));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::scanUploaded(const Storage::UploadedSecure &data) {
 | 
			
		||||
	if (const auto edit = findEditFile(data.fullId)) {
 | 
			
		||||
		Assert(edit->uploaded != nullptr);
 | 
			
		||||
 | 
			
		||||
		edit->fields.id = edit->uploaded->fileId = data.fileId;
 | 
			
		||||
		edit->fields.size = edit->uploaded->bytes.size();
 | 
			
		||||
		edit->fields.dcId = MTP::maindc();
 | 
			
		||||
		edit->uploaded->partsCount = data.partsCount;
 | 
			
		||||
		edit->fields.bytes = std::move(edit->uploaded->bytes);
 | 
			
		||||
		edit->fields.fileHash = std::move(edit->uploaded->hash);
 | 
			
		||||
		edit->uploaded->fullId = FullMsgId();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rpl::producer<> FormController::secretReadyEvents() const {
 | 
			
		||||
	return _secretReady.events();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -111,6 +247,10 @@ QString FormController::defaultPhoneNumber() const {
 | 
			
		|||
	return QString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rpl::producer<ScanInfo> FormController::scanUpdated() const {
 | 
			
		||||
	return _scanUpdated.events();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::fillRows(
 | 
			
		||||
	base::lambda<void(
 | 
			
		||||
		QString title,
 | 
			
		||||
| 
						 | 
				
			
			@ -150,10 +290,24 @@ void FormController::editField(int index) {
 | 
			
		|||
	Expects(index >= 0 && index < _form.fields.size());
 | 
			
		||||
 | 
			
		||||
	auto box = [&]() -> object_ptr<BoxContent> {
 | 
			
		||||
		const auto &field = _form.fields[index];
 | 
			
		||||
		auto &field = _form.fields[index];
 | 
			
		||||
		switch (field.type) {
 | 
			
		||||
		case Field::Type::Identity:
 | 
			
		||||
			return Box<IdentityBox>(this, index, fieldDataIdentity(field));
 | 
			
		||||
			if (field.document) {
 | 
			
		||||
				loadFiles(field.document->files);
 | 
			
		||||
			} else {
 | 
			
		||||
				field.document = Value();
 | 
			
		||||
			}
 | 
			
		||||
			field.document->filesInEdit = ranges::view::all(
 | 
			
		||||
				field.document->files
 | 
			
		||||
			) | ranges::view::transform([](const File &file) {
 | 
			
		||||
				return EditFile(file, nullptr);
 | 
			
		||||
			}) | ranges::to_vector;
 | 
			
		||||
			return Box<IdentityBox>(
 | 
			
		||||
				this,
 | 
			
		||||
				index,
 | 
			
		||||
				fieldDataIdentity(field),
 | 
			
		||||
				fieldFilesIdentity(field));
 | 
			
		||||
		}
 | 
			
		||||
		return { nullptr };
 | 
			
		||||
	}();
 | 
			
		||||
| 
						 | 
				
			
			@ -162,6 +316,56 @@ void FormController::editField(int index) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::loadFiles(const std::vector<File> &files) {
 | 
			
		||||
	for (const auto &file : files) {
 | 
			
		||||
		const auto key = FileKey{ file.id, file.dcId };
 | 
			
		||||
		const auto i = _fileLoaders.find(key);
 | 
			
		||||
		if (i == _fileLoaders.end()) {
 | 
			
		||||
			const auto [i, ok] = _fileLoaders.emplace(
 | 
			
		||||
				key,
 | 
			
		||||
				std::make_unique<mtpFileLoader>(
 | 
			
		||||
					file.dcId,
 | 
			
		||||
					file.id,
 | 
			
		||||
					file.accessHash,
 | 
			
		||||
					0,
 | 
			
		||||
					SecureFileLocation,
 | 
			
		||||
					QString(),
 | 
			
		||||
					file.size,
 | 
			
		||||
					LoadToCacheAsWell,
 | 
			
		||||
					LoadFromCloudOrLocal,
 | 
			
		||||
					false));
 | 
			
		||||
			const auto loader = i->second.get();
 | 
			
		||||
			loader->connect(loader, &mtpFileLoader::progress, [=] {
 | 
			
		||||
				if (loader->finished()) {
 | 
			
		||||
					fileLoaded(key, loader->bytes());
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			loader->connect(loader, &mtpFileLoader::failed, [=] {
 | 
			
		||||
			});
 | 
			
		||||
			loader->start();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::fileLoaded(FileKey key, const QByteArray &bytes) {
 | 
			
		||||
	if (const auto [field, file] = findFile(key); file != nullptr) {
 | 
			
		||||
		const auto decrypted = DecryptData(
 | 
			
		||||
			gsl::as_bytes(gsl::make_span(bytes)),
 | 
			
		||||
			file->fileHash,
 | 
			
		||||
			field->document->filesSecret);
 | 
			
		||||
		auto image = App::readImage(QByteArray::fromRawData(
 | 
			
		||||
			reinterpret_cast<const char*>(decrypted.data()),
 | 
			
		||||
			decrypted.size()));
 | 
			
		||||
		if (!image.isNull()) {
 | 
			
		||||
			_scanUpdated.fire({
 | 
			
		||||
				FileKey{ file->id, file->dcId },
 | 
			
		||||
				QString("loaded"),
 | 
			
		||||
				std::move(image),
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
IdentityData FormController::fieldDataIdentity(const Field &field) const {
 | 
			
		||||
	const auto &map = field.data.values;
 | 
			
		||||
	auto result = IdentityData();
 | 
			
		||||
| 
						 | 
				
			
			@ -174,6 +378,20 @@ IdentityData FormController::fieldDataIdentity(const Field &field) const {
 | 
			
		|||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<ScanInfo> FormController::fieldFilesIdentity(
 | 
			
		||||
		const Field &field) const {
 | 
			
		||||
	Expects(field.document.has_value());
 | 
			
		||||
 | 
			
		||||
	auto result = std::vector<ScanInfo>();
 | 
			
		||||
	for (const auto &file : field.document->filesInEdit) {
 | 
			
		||||
		result.push_back({
 | 
			
		||||
			FileKey{ file.fields.id, file.fields.dcId },
 | 
			
		||||
			langDateTime(QDateTime::currentDateTime()),
 | 
			
		||||
			QImage() });
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::saveFieldIdentity(
 | 
			
		||||
		int index,
 | 
			
		||||
		const IdentityData &data) {
 | 
			
		||||
| 
						 | 
				
			
			@ -185,24 +403,25 @@ void FormController::saveFieldIdentity(
 | 
			
		|||
	_form.fields[index].data.values[qsl("last_name")] = data.surname;
 | 
			
		||||
 | 
			
		||||
	saveData(index);
 | 
			
		||||
	saveFiles(index);
 | 
			
		||||
 | 
			
		||||
	_editBox->closeBox();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::map<QString, QString> FormController::fillData(
 | 
			
		||||
		const Value &from) const {
 | 
			
		||||
	if (from.data.isEmpty()) {
 | 
			
		||||
	if (from.dataEncrypted.isEmpty()) {
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
	const auto valueHash = gsl::as_bytes(gsl::make_span(from.dataHash));
 | 
			
		||||
	const auto valueSecret = DecryptValueSecret(
 | 
			
		||||
		gsl::as_bytes(gsl::make_span(from.dataSecret)),
 | 
			
		||||
		gsl::as_bytes(gsl::make_span(from.dataSecretEncrypted)),
 | 
			
		||||
		_secret,
 | 
			
		||||
		valueHash);
 | 
			
		||||
	return DecryptData(
 | 
			
		||||
		gsl::as_bytes(gsl::make_span(from.data)),
 | 
			
		||||
	return DeserializeData(DecryptData(
 | 
			
		||||
		gsl::as_bytes(gsl::make_span(from.dataEncrypted)),
 | 
			
		||||
		valueHash,
 | 
			
		||||
		valueSecret);
 | 
			
		||||
		valueSecret));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::saveData(int index) {
 | 
			
		||||
| 
						 | 
				
			
			@ -215,8 +434,7 @@ void FormController::saveData(int index) {
 | 
			
		|||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	const auto &data = _form.fields[index].data;
 | 
			
		||||
	const auto valueSecret = GenerateSecretBytes();
 | 
			
		||||
	const auto encrypted = EncryptData(valueSecret, data.values);
 | 
			
		||||
	const auto encrypted = EncryptData(SerializeData(data.values));
 | 
			
		||||
 | 
			
		||||
	// #TODO file_hash + file_hash + ...
 | 
			
		||||
	// PrepareValueHash(encrypted.hash, valueSecret);
 | 
			
		||||
| 
						 | 
				
			
			@ -234,7 +452,7 @@ void FormController::saveData(int index) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	const auto encryptedValueSecret = EncryptValueSecret(
 | 
			
		||||
		valueSecret,
 | 
			
		||||
		encrypted.secret,
 | 
			
		||||
		_secret,
 | 
			
		||||
		valueHash);
 | 
			
		||||
	request(MTPaccount_SaveSecureValue(MTP_inputSecureValueData(
 | 
			
		||||
| 
						 | 
				
			
			@ -249,11 +467,90 @@ void FormController::saveData(int index) {
 | 
			
		|||
			Ui::show(Box<InformBox>("Verification needed :("), LayerOption::KeepOther);
 | 
			
		||||
		}
 | 
			
		||||
	}).fail([=](const RPCError &error) {
 | 
			
		||||
		// #TODO
 | 
			
		||||
		Ui::show(Box<InformBox>("Error saving value."));
 | 
			
		||||
	}).send();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::saveFiles(int index) {
 | 
			
		||||
	Expects(index >= 0 && index < _form.fields.size());
 | 
			
		||||
	Expects(_form.fields[index].document.has_value());
 | 
			
		||||
 | 
			
		||||
	if (_secret.empty()) {
 | 
			
		||||
		generateSecret([=] {
 | 
			
		||||
			saveFiles(index);
 | 
			
		||||
		});
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	auto &document = *_form.fields[index].document;
 | 
			
		||||
	if (document.filesSecret.empty()) {
 | 
			
		||||
		Assert(document.filesInEdit.empty());
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	auto filesHash = computeFilesHash(
 | 
			
		||||
		ranges::view::all(
 | 
			
		||||
			document.filesInEdit
 | 
			
		||||
		) | ranges::view::transform([=](const EditFile &file) {
 | 
			
		||||
			return gsl::as_bytes(gsl::make_span(file.fields.fileHash));
 | 
			
		||||
		}),
 | 
			
		||||
		document.filesSecret);
 | 
			
		||||
 | 
			
		||||
	auto filesHashString = QString();
 | 
			
		||||
	filesHashString.reserve(filesHash.size() * 2);
 | 
			
		||||
	const auto hex = [](uchar value) -> QChar {
 | 
			
		||||
		return (value >= 10) ? ('a' + (value - 10)) : ('0' + value);
 | 
			
		||||
	};
 | 
			
		||||
	for (const auto byte : filesHash) {
 | 
			
		||||
		const auto value = uchar(byte);
 | 
			
		||||
		const auto high = uchar(value / 16);
 | 
			
		||||
		const auto low = uchar(value % 16);
 | 
			
		||||
		filesHashString.append(hex(high)).append(hex(low));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto files = QVector<MTPInputSecureFile>();
 | 
			
		||||
	files.reserve(document.filesInEdit.size());
 | 
			
		||||
	for (const auto &file : document.filesInEdit) {
 | 
			
		||||
		if (const auto uploaded = file.uploaded.get()) {
 | 
			
		||||
			auto fileHashString = QString();
 | 
			
		||||
			for (const auto byte : file.fields.fileHash) {
 | 
			
		||||
				const auto value = uchar(byte);
 | 
			
		||||
				const auto high = uchar(value / 16);
 | 
			
		||||
				const auto low = uchar(value % 16);
 | 
			
		||||
				fileHashString.append(hex(high)).append(hex(low));
 | 
			
		||||
			}
 | 
			
		||||
			files.push_back(MTP_inputSecureFileUploaded(
 | 
			
		||||
				MTP_long(file.fields.id),
 | 
			
		||||
				MTP_int(uploaded->partsCount),
 | 
			
		||||
				MTP_bytes(uploaded->md5checksum),
 | 
			
		||||
				MTP_string(fileHashString)));
 | 
			
		||||
		} else {
 | 
			
		||||
			files.push_back(MTP_inputSecureFile(
 | 
			
		||||
				MTP_long(file.fields.id),
 | 
			
		||||
				MTP_long(file.fields.accessHash)));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto encryptedFilesSecret = EncryptValueSecret(
 | 
			
		||||
		document.filesSecret,
 | 
			
		||||
		_secret,
 | 
			
		||||
		filesHash);
 | 
			
		||||
	request(MTPaccount_SaveSecureValue(MTP_inputSecureValueFile(
 | 
			
		||||
		MTP_string(document.name),
 | 
			
		||||
		MTP_vector<MTPInputSecureFile>(files),
 | 
			
		||||
		MTP_string(filesHashString),
 | 
			
		||||
		MTP_bytes(encryptedFilesSecret)
 | 
			
		||||
	))).done([=](const MTPaccount_SecureValueResult &result) {
 | 
			
		||||
		if (result.type() == mtpc_account_secureValueResultSaved) {
 | 
			
		||||
			Ui::show(Box<InformBox>("Files Saved"), LayerOption::KeepOther);
 | 
			
		||||
		} else if (result.type() == mtpc_account_secureValueVerificationNeeded) {
 | 
			
		||||
			Ui::show(Box<InformBox>("Verification needed :("), LayerOption::KeepOther);
 | 
			
		||||
		}
 | 
			
		||||
	}).fail([=](const RPCError &error) {
 | 
			
		||||
		Ui::show(Box<InformBox>("Error saving files."));
 | 
			
		||||
	}).send();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::generateSecret(base::lambda<void()> callback) {
 | 
			
		||||
	_secretCallbacks.push_back(callback);
 | 
			
		||||
	if (_saveSecretRequestId) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +571,9 @@ void FormController::generateSecret(base::lambda<void()> callback) {
 | 
			
		|||
	)).done([=](const MTPBool &result) {
 | 
			
		||||
		_saveSecretRequestId = 0;
 | 
			
		||||
		_secret = secret;
 | 
			
		||||
		for (const auto &callback : base::take(_secretCallbacks)) {
 | 
			
		||||
			callback();
 | 
			
		||||
		}
 | 
			
		||||
	}).fail([=](const RPCError &error) {
 | 
			
		||||
		// #TODO wrong password hash error?
 | 
			
		||||
		Ui::show(Box<InformBox>("Saving encrypted value failed."));
 | 
			
		||||
| 
						 | 
				
			
			@ -319,7 +618,7 @@ auto FormController::convertValue(
 | 
			
		|||
	case mtpc_secureValueData: {
 | 
			
		||||
		const auto &data = value.c_secureValueData();
 | 
			
		||||
		result.name = qs(data.vname);
 | 
			
		||||
		result.data = data.vdata.v;
 | 
			
		||||
		result.dataEncrypted = data.vdata.v;
 | 
			
		||||
		const auto hashString = qs(data.vhash);
 | 
			
		||||
		for (auto i = 0, count = hashString.size(); i + 1 < count; i += 2) {
 | 
			
		||||
			auto digit = [&](QChar ch) -> int {
 | 
			
		||||
| 
						 | 
				
			
			@ -344,7 +643,7 @@ auto FormController::convertValue(
 | 
			
		|||
			result.dataHash.clear();
 | 
			
		||||
		}
 | 
			
		||||
//		result.dataHash = data.vhash.v;
 | 
			
		||||
		result.dataSecret = data.vsecret.v;
 | 
			
		||||
		result.dataSecretEncrypted = data.vsecret.v;
 | 
			
		||||
	} break;
 | 
			
		||||
	case mtpc_secureValueText: {
 | 
			
		||||
		const auto &data = value.c_secureValueText();
 | 
			
		||||
| 
						 | 
				
			
			@ -355,8 +654,31 @@ auto FormController::convertValue(
 | 
			
		|||
	case mtpc_secureValueFile: {
 | 
			
		||||
		const auto &data = value.c_secureValueFile();
 | 
			
		||||
		result.name = qs(data.vname);
 | 
			
		||||
		result.filesHash = data.vhash.v;
 | 
			
		||||
		result.filesSecret = data.vsecret.v;
 | 
			
		||||
		const auto hashString = qs(data.vhash);
 | 
			
		||||
		for (auto i = 0, count = hashString.size(); i + 1 < count; i += 2) {
 | 
			
		||||
			auto digit = [&](QChar ch) -> int {
 | 
			
		||||
				const auto code = ch.unicode();
 | 
			
		||||
				if (code >= 'a' && code <= 'f') {
 | 
			
		||||
					return (code - 'a') + 10;
 | 
			
		||||
				} else if (code >= 'A' && code <= 'F') {
 | 
			
		||||
					return (code - 'A') + 10;
 | 
			
		||||
				} else if (code >= '0' && code <= '9') {
 | 
			
		||||
					return (code - '0');
 | 
			
		||||
				}
 | 
			
		||||
				return -1;
 | 
			
		||||
			};
 | 
			
		||||
			const auto ch1 = digit(hashString[i]);
 | 
			
		||||
			const auto ch2 = digit(hashString[i + 1]);
 | 
			
		||||
			if (ch1 >= 0 && ch2 >= 0) {
 | 
			
		||||
				const auto byte = ch1 * 16 + ch2;
 | 
			
		||||
				result.filesHash.push_back(byte);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if (result.filesHash.size() != 32) {
 | 
			
		||||
			result.filesHash.clear();
 | 
			
		||||
		}
 | 
			
		||||
//		result.filesHash = data.vhash.v;
 | 
			
		||||
		result.filesSecretEncrypted = data.vsecret.v;
 | 
			
		||||
		for (const auto &file : data.vfile.v) {
 | 
			
		||||
			switch (file.type()) {
 | 
			
		||||
			case mtpc_secureFileEmpty: {
 | 
			
		||||
| 
						 | 
				
			
			@ -369,7 +691,30 @@ auto FormController::convertValue(
 | 
			
		|||
				normal.accessHash = fields.vaccess_hash.v;
 | 
			
		||||
				normal.size = fields.vsize.v;
 | 
			
		||||
				normal.dcId = fields.vdc_id.v;
 | 
			
		||||
				normal.fileHash = qba(fields.vfile_hash);
 | 
			
		||||
				const auto fileHashString = qs(fields.vfile_hash);
 | 
			
		||||
				for (auto i = 0, count = fileHashString.size(); i + 1 < count; i += 2) {
 | 
			
		||||
					auto digit = [&](QChar ch) -> int {
 | 
			
		||||
						const auto code = ch.unicode();
 | 
			
		||||
						if (code >= 'a' && code <= 'f') {
 | 
			
		||||
							return (code - 'a') + 10;
 | 
			
		||||
						} else if (code >= 'A' && code <= 'F') {
 | 
			
		||||
							return (code - 'A') + 10;
 | 
			
		||||
						} else if (code >= '0' && code <= '9') {
 | 
			
		||||
							return (code - '0');
 | 
			
		||||
						}
 | 
			
		||||
						return -1;
 | 
			
		||||
					};
 | 
			
		||||
					const auto ch1 = digit(fileHashString[i]);
 | 
			
		||||
					const auto ch2 = digit(fileHashString[i + 1]);
 | 
			
		||||
					if (ch1 >= 0 && ch2 >= 0) {
 | 
			
		||||
						const auto byte = ch1 * 16 + ch2;
 | 
			
		||||
						normal.fileHash.push_back(gsl::byte(byte));
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if (normal.fileHash.size() != 32) {
 | 
			
		||||
					normal.fileHash.clear();
 | 
			
		||||
				}
 | 
			
		||||
//				normal.fileHash = byteVectorFromMTP(fields.vfile_hash);
 | 
			
		||||
				result.files.push_back(std::move(normal));
 | 
			
		||||
			} break;
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -379,6 +724,33 @@ auto FormController::convertValue(
 | 
			
		|||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
 | 
			
		||||
	for (auto &field : _form.fields) {
 | 
			
		||||
		if (auto &document = field.document) {
 | 
			
		||||
			for (auto &file : document->filesInEdit) {
 | 
			
		||||
				if (file.uploaded && file.uploaded->fullId == fullId) {
 | 
			
		||||
					return &file;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto FormController::findFile(const FileKey &key)
 | 
			
		||||
-> std::pair<Field*, File*> {
 | 
			
		||||
	for (auto &field : _form.fields) {
 | 
			
		||||
		if (auto &document = field.document) {
 | 
			
		||||
			for (auto &file : document->files) {
 | 
			
		||||
				if (file.dcId == key.dcId && file.id == key.id) {
 | 
			
		||||
					return { &field, &file };
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return { nullptr, nullptr };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FormController::formDone(const MTPaccount_AuthorizationForm &result) {
 | 
			
		||||
	parseForm(result);
 | 
			
		||||
	if (!_passwordRequestId) {
 | 
			
		||||
| 
						 | 
				
			
			@ -479,4 +851,6 @@ void FormController::parsePassword(const MTPDaccount_password &result) {
 | 
			
		|||
		gsl::as_bytes(gsl::make_span(result.vsecret_random.v)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FormController::~FormController() = default;
 | 
			
		||||
 | 
			
		||||
} // namespace Passport
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,9 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "mtproto/sender.h"
 | 
			
		||||
#include "base/weak_ptr.h"
 | 
			
		||||
 | 
			
		||||
class BoxContent;
 | 
			
		||||
 | 
			
		||||
namespace Storage {
 | 
			
		||||
struct UploadedSecure;
 | 
			
		||||
} // namespace Storage
 | 
			
		||||
 | 
			
		||||
namespace Window {
 | 
			
		||||
class Controller;
 | 
			
		||||
} // namespace Window
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +38,39 @@ struct FormRequest {
 | 
			
		|||
 | 
			
		||||
struct IdentityData;
 | 
			
		||||
 | 
			
		||||
class FormController : private MTP::Sender {
 | 
			
		||||
struct FileKey {
 | 
			
		||||
	uint64 id = 0;
 | 
			
		||||
	int32 dcId = 0;
 | 
			
		||||
 | 
			
		||||
	inline bool operator==(const FileKey &other) const {
 | 
			
		||||
		return (id == other.id) && (dcId == other.dcId);
 | 
			
		||||
	}
 | 
			
		||||
	inline bool operator!=(const FileKey &other) const {
 | 
			
		||||
		return !(*this == other);
 | 
			
		||||
	}
 | 
			
		||||
	inline bool operator<(const FileKey &other) const {
 | 
			
		||||
		return (id < other.id) || ((id == other.id) && (dcId < other.dcId));
 | 
			
		||||
	}
 | 
			
		||||
	inline bool operator>(const FileKey &other) const {
 | 
			
		||||
		return (other < *this);
 | 
			
		||||
	}
 | 
			
		||||
	inline bool operator<=(const FileKey &other) const {
 | 
			
		||||
		return !(other < *this);
 | 
			
		||||
	}
 | 
			
		||||
	inline bool operator>=(const FileKey &other) const {
 | 
			
		||||
		return !(*this < other);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ScanInfo {
 | 
			
		||||
	FileKey key;
 | 
			
		||||
	QString date;
 | 
			
		||||
	QImage thumb;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class FormController : private MTP::Sender, public base::has_weak_ptr {
 | 
			
		||||
public:
 | 
			
		||||
	FormController(
 | 
			
		||||
		not_null<Window::Controller*> controller,
 | 
			
		||||
| 
						 | 
				
			
			@ -45,10 +82,13 @@ public:
 | 
			
		|||
	rpl::producer<QString> passwordError() const;
 | 
			
		||||
	QString passwordHint() const;
 | 
			
		||||
 | 
			
		||||
	void uploadScan(int index, QByteArray &&content);
 | 
			
		||||
 | 
			
		||||
	rpl::producer<> secretReadyEvents() const;
 | 
			
		||||
 | 
			
		||||
	QString defaultEmail() const;
 | 
			
		||||
	QString defaultPhoneNumber() const;
 | 
			
		||||
	rpl::producer<ScanInfo> scanUpdated() const;
 | 
			
		||||
 | 
			
		||||
	void fillRows(
 | 
			
		||||
		base::lambda<void(
 | 
			
		||||
| 
						 | 
				
			
			@ -59,20 +99,41 @@ public:
 | 
			
		|||
 | 
			
		||||
	void saveFieldIdentity(int index, const IdentityData &data);
 | 
			
		||||
 | 
			
		||||
	~FormController();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	struct UploadedScan {
 | 
			
		||||
		~UploadedScan();
 | 
			
		||||
 | 
			
		||||
		FullMsgId fullId;
 | 
			
		||||
		uint64 fileId = 0;
 | 
			
		||||
		int partsCount = 0;
 | 
			
		||||
		QByteArray md5checksum;
 | 
			
		||||
		base::byte_vector hash;
 | 
			
		||||
		base::byte_vector bytes;
 | 
			
		||||
	};
 | 
			
		||||
	struct File {
 | 
			
		||||
		uint64 id = 0;
 | 
			
		||||
		uint64 accessHash = 0;
 | 
			
		||||
		int32 size = 0;
 | 
			
		||||
		int32 dcId = 0;
 | 
			
		||||
		QByteArray fileHash;
 | 
			
		||||
		base::byte_vector fileHash;
 | 
			
		||||
		base::byte_vector bytes;
 | 
			
		||||
	};
 | 
			
		||||
	struct EditFile {
 | 
			
		||||
		EditFile(
 | 
			
		||||
			const File &fields,
 | 
			
		||||
			std::unique_ptr<UploadedScan> &&uploaded);
 | 
			
		||||
 | 
			
		||||
		File fields;
 | 
			
		||||
		std::unique_ptr<UploadedScan> uploaded;
 | 
			
		||||
	};
 | 
			
		||||
	struct Value {
 | 
			
		||||
		QString name;
 | 
			
		||||
 | 
			
		||||
		QByteArray data;
 | 
			
		||||
		QByteArray dataEncrypted;
 | 
			
		||||
		QByteArray dataHash;
 | 
			
		||||
		QByteArray dataSecret;
 | 
			
		||||
		QByteArray dataSecretEncrypted;
 | 
			
		||||
		std::map<QString, QString> values;
 | 
			
		||||
 | 
			
		||||
		QString text;
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +141,10 @@ private:
 | 
			
		|||
 | 
			
		||||
		std::vector<File> files;
 | 
			
		||||
		QByteArray filesHash;
 | 
			
		||||
		QByteArray filesSecret;
 | 
			
		||||
		QByteArray filesSecretEncrypted;
 | 
			
		||||
		base::byte_vector filesSecret;
 | 
			
		||||
 | 
			
		||||
		std::vector<EditFile> filesInEdit;
 | 
			
		||||
	};
 | 
			
		||||
	struct Field {
 | 
			
		||||
		enum class Type {
 | 
			
		||||
| 
						 | 
				
			
			@ -90,6 +154,7 @@ private:
 | 
			
		|||
			Email,
 | 
			
		||||
		};
 | 
			
		||||
		explicit Field(Type type);
 | 
			
		||||
		Field(Field &&other) = default;
 | 
			
		||||
 | 
			
		||||
		Type type;
 | 
			
		||||
		Value data;
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +172,8 @@ private:
 | 
			
		|||
		bool hasRecovery = false;
 | 
			
		||||
	};
 | 
			
		||||
	Value convertValue(const MTPSecureValue &value) const;
 | 
			
		||||
	EditFile *findEditFile(const FullMsgId &fullId);
 | 
			
		||||
	std::pair<Field*, File*> findFile(const FileKey &key);
 | 
			
		||||
 | 
			
		||||
	void requestForm();
 | 
			
		||||
	void requestPassword();
 | 
			
		||||
| 
						 | 
				
			
			@ -122,11 +189,24 @@ private:
 | 
			
		|||
	void parsePassword(const MTPDaccount_password &settings);
 | 
			
		||||
 | 
			
		||||
	IdentityData fieldDataIdentity(const Field &field) const;
 | 
			
		||||
	std::vector<ScanInfo> fieldFilesIdentity(const Field &field) const;
 | 
			
		||||
 | 
			
		||||
	void loadFiles(const std::vector<File> &files);
 | 
			
		||||
	void fileLoaded(FileKey key, const QByteArray &bytes);
 | 
			
		||||
	std::map<QString, QString> fillData(const Value &from) const;
 | 
			
		||||
	void saveData(int index);
 | 
			
		||||
	void saveFiles(int index);
 | 
			
		||||
	void generateSecret(base::lambda<void()> callback);
 | 
			
		||||
 | 
			
		||||
	template <typename FileHashes>
 | 
			
		||||
	base::byte_vector computeFilesHash(
 | 
			
		||||
		FileHashes fileHashes,
 | 
			
		||||
		base::const_byte_span valueHash);
 | 
			
		||||
 | 
			
		||||
	void subscribeToUploader();
 | 
			
		||||
	void uploadEncryptedScan(int index, UploadedScan &&data);
 | 
			
		||||
	void scanUploaded(const Storage::UploadedSecure &data);
 | 
			
		||||
 | 
			
		||||
	not_null<Window::Controller*> _controller;
 | 
			
		||||
	FormRequest _request;
 | 
			
		||||
	UserData *_bot = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -138,10 +218,13 @@ private:
 | 
			
		|||
 | 
			
		||||
	PasswordSettings _password;
 | 
			
		||||
	Form _form;
 | 
			
		||||
	std::map<FileKey, std::unique_ptr<mtpFileLoader>> _fileLoaders;
 | 
			
		||||
	rpl::event_stream<ScanInfo> _scanUpdated;
 | 
			
		||||
 | 
			
		||||
	base::byte_vector _passwordHashForSecret;
 | 
			
		||||
	base::byte_vector _passwordHashForAuth;
 | 
			
		||||
	base::byte_vector _secret;
 | 
			
		||||
	std::vector<base::lambda<void()>> _secretCallbacks;
 | 
			
		||||
	mtpRequestId _saveSecretRequestId = 0;
 | 
			
		||||
	QString _passwordEmail;
 | 
			
		||||
	rpl::event_stream<> _secretReady;
 | 
			
		||||
| 
						 | 
				
			
			@ -149,6 +232,8 @@ private:
 | 
			
		|||
 | 
			
		||||
	QPointer<BoxContent> _editBox;
 | 
			
		||||
 | 
			
		||||
	rpl::lifetime _uploaderSubscriptions;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace Passport
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -493,6 +493,8 @@ void mtpFileLoader::makeRequest(int offset) {
 | 
			
		|||
MTPInputFileLocation mtpFileLoader::computeLocation() const {
 | 
			
		||||
	if (_location) {
 | 
			
		||||
		return MTP_inputFileLocation(MTP_long(_location->volume()), MTP_int(_location->local()), MTP_long(_location->secret()));
 | 
			
		||||
	} else if (_locationType == SecureFileLocation) {
 | 
			
		||||
		return MTP_inputSecureFileLocation(MTP_long(_id), MTP_long(_accessHash));
 | 
			
		||||
	}
 | 
			
		||||
	return MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_accessHash), MTP_int(_version));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,8 @@ Uploader::File::File(const SendMediaReady &media) : media(media) {
 | 
			
		|||
}
 | 
			
		||||
Uploader::File::File(const std::shared_ptr<FileLoadResult> &file)
 | 
			
		||||
: file(file) {
 | 
			
		||||
	partsCount = (type() == SendMediaType::Photo)
 | 
			
		||||
	partsCount = (type() == SendMediaType::Photo
 | 
			
		||||
		|| type() == SendMediaType::Secure)
 | 
			
		||||
		? file->fileparts.size()
 | 
			
		||||
		: file->thumbparts.size();
 | 
			
		||||
	if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
 | 
			
		||||
| 
						 | 
				
			
			@ -160,13 +161,18 @@ void Uploader::currentFailed() {
 | 
			
		|||
	auto j = queue.find(uploadingId);
 | 
			
		||||
	if (j != queue.end()) {
 | 
			
		||||
		if (j->second.type() == SendMediaType::Photo) {
 | 
			
		||||
			emit photoFailed(j->first);
 | 
			
		||||
		} else if (j->second.type() == SendMediaType::File) {
 | 
			
		||||
			_photoFailed.fire_copy(j->first);
 | 
			
		||||
		} else if (j->second.type() == SendMediaType::File
 | 
			
		||||
			|| j->second.type() == SendMediaType::Audio) {
 | 
			
		||||
			const auto document = Auth().data().document(j->second.id());
 | 
			
		||||
			if (document->uploading()) {
 | 
			
		||||
				document->status = FileUploadFailed;
 | 
			
		||||
			}
 | 
			
		||||
			emit documentFailed(j->first);
 | 
			
		||||
			_documentFailed.fire_copy(j->first);
 | 
			
		||||
		} else if (j->second.type() == SendMediaType::Secure) {
 | 
			
		||||
			_secureFailed.fire_copy(j->first);
 | 
			
		||||
		} else {
 | 
			
		||||
			Unexpected("Type in Uploader::currentFailed.");
 | 
			
		||||
		}
 | 
			
		||||
		queue.erase(j);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -220,12 +226,14 @@ void Uploader::sendNext() {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	auto &parts = uploadingData.file
 | 
			
		||||
		? (uploadingData.type() == SendMediaType::Photo
 | 
			
		||||
		? ((uploadingData.type() == SendMediaType::Photo
 | 
			
		||||
			|| uploadingData.type() == SendMediaType::Secure)
 | 
			
		||||
			? uploadingData.file->fileparts
 | 
			
		||||
			: uploadingData.file->thumbparts)
 | 
			
		||||
		: uploadingData.media.parts;
 | 
			
		||||
	const auto partsOfId = uploadingData.file
 | 
			
		||||
		? (uploadingData.type() == SendMediaType::Photo
 | 
			
		||||
		? ((uploadingData.type() == SendMediaType::Photo
 | 
			
		||||
			|| uploadingData.type() == SendMediaType::Secure)
 | 
			
		||||
			? uploadingData.file->id
 | 
			
		||||
			: uploadingData.file->thumbId)
 | 
			
		||||
		: uploadingData.media.thumbId;
 | 
			
		||||
| 
						 | 
				
			
			@ -250,7 +258,7 @@ void Uploader::sendNext() {
 | 
			
		|||
						MTP_int(uploadingData.partsCount),
 | 
			
		||||
						MTP_string(photoFilename),
 | 
			
		||||
						MTP_bytes(md5));
 | 
			
		||||
					emit photoReady(uploadingId, silent, file);
 | 
			
		||||
					_photoReady.fire({ uploadingId, silent, file });
 | 
			
		||||
				} else if (uploadingData.type() == SendMediaType::File
 | 
			
		||||
					|| uploadingData.type() == SendMediaType::Audio) {
 | 
			
		||||
					QByteArray docMd5(32, Qt::Uninitialized);
 | 
			
		||||
| 
						 | 
				
			
			@ -278,14 +286,19 @@ void Uploader::sendNext() {
 | 
			
		|||
							MTP_int(uploadingData.partsCount),
 | 
			
		||||
							MTP_string(thumbFilename),
 | 
			
		||||
							MTP_bytes(thumbMd5));
 | 
			
		||||
						emit thumbDocumentReady(
 | 
			
		||||
						_thumbDocumentReady.fire({
 | 
			
		||||
							uploadingId,
 | 
			
		||||
							silent,
 | 
			
		||||
							file,
 | 
			
		||||
							thumb);
 | 
			
		||||
							thumb });
 | 
			
		||||
					} else {
 | 
			
		||||
						emit documentReady(uploadingId, silent, file);
 | 
			
		||||
						_documentReady.fire({ uploadingId, silent, file });
 | 
			
		||||
					}
 | 
			
		||||
				} else if (uploadingData.type() == SendMediaType::Secure) {
 | 
			
		||||
					_secureReady.fire({
 | 
			
		||||
						uploadingId,
 | 
			
		||||
						uploadingData.id(),
 | 
			
		||||
						uploadingData.partsCount });
 | 
			
		||||
				}
 | 
			
		||||
				queue.erase(uploadingId);
 | 
			
		||||
				uploadingId = FullMsgId();
 | 
			
		||||
| 
						 | 
				
			
			@ -457,7 +470,7 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
 | 
			
		|||
					photo->uploadingData->size = file.file->partssize;
 | 
			
		||||
					photo->uploadingData->offset = file.fileSentSize;
 | 
			
		||||
				}
 | 
			
		||||
				emit photoProgress(fullId);
 | 
			
		||||
				_photoProgress.fire_copy(fullId);
 | 
			
		||||
			} else if (file.type() == SendMediaType::File
 | 
			
		||||
				|| file.type() == SendMediaType::Audio) {
 | 
			
		||||
				const auto document = Auth().data().document(file.id());
 | 
			
		||||
| 
						 | 
				
			
			@ -468,7 +481,9 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
 | 
			
		|||
						document->uploadingData->size,
 | 
			
		||||
						doneParts * file.docPartSize);
 | 
			
		||||
				}
 | 
			
		||||
				emit documentProgress(fullId);
 | 
			
		||||
				_documentProgress.fire_copy(fullId);
 | 
			
		||||
			} else if (file.type() == SendMediaType::Secure) {
 | 
			
		||||
				_secureProgress.fire_copy(fullId);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,31 @@ struct SendMediaReady;
 | 
			
		|||
 | 
			
		||||
namespace Storage {
 | 
			
		||||
 | 
			
		||||
struct UploadedPhoto {
 | 
			
		||||
	FullMsgId fullId;
 | 
			
		||||
	bool silent = false;
 | 
			
		||||
	MTPInputFile file;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct UploadedDocument {
 | 
			
		||||
	FullMsgId fullId;
 | 
			
		||||
	bool silent = false;
 | 
			
		||||
	MTPInputFile file;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct UploadedThumbDocument {
 | 
			
		||||
	FullMsgId fullId;
 | 
			
		||||
	bool silent = false;
 | 
			
		||||
	MTPInputFile file;
 | 
			
		||||
	MTPInputFile thumb;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct UploadedSecure {
 | 
			
		||||
	FullMsgId fullId;
 | 
			
		||||
	uint64 fileId = 0;
 | 
			
		||||
	int partsCount = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Uploader : public QObject, public RPCSender {
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +56,37 @@ public:
 | 
			
		|||
 | 
			
		||||
	void clear();
 | 
			
		||||
 | 
			
		||||
	rpl::producer<UploadedPhoto> photoReady() const {
 | 
			
		||||
		return _photoReady.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<UploadedDocument> documentReady() const {
 | 
			
		||||
		return _documentReady.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<UploadedThumbDocument> thumbDocumentReady() const {
 | 
			
		||||
		return _thumbDocumentReady.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<UploadedSecure> secureReady() const {
 | 
			
		||||
		return _secureReady.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<FullMsgId> photoProgress() const {
 | 
			
		||||
		return _photoProgress.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<FullMsgId> documentProgress() const {
 | 
			
		||||
		return _documentProgress.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<FullMsgId> secureProgress() const {
 | 
			
		||||
		return _secureProgress.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<FullMsgId> photoFailed() const {
 | 
			
		||||
		return _photoFailed.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<FullMsgId> documentFailed() const {
 | 
			
		||||
		return _documentFailed.events();
 | 
			
		||||
	}
 | 
			
		||||
	rpl::producer<FullMsgId> secureFailed() const {
 | 
			
		||||
		return _secureFailed.events();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	~Uploader();
 | 
			
		||||
 | 
			
		||||
public slots:
 | 
			
		||||
| 
						 | 
				
			
			@ -38,17 +94,6 @@ public slots:
 | 
			
		|||
	void sendNext();
 | 
			
		||||
	void stopSessions();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void photoReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
 | 
			
		||||
	void documentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file);
 | 
			
		||||
	void thumbDocumentReady(const FullMsgId &msgId, bool silent, const MTPInputFile &file, const MTPInputFile &thumb);
 | 
			
		||||
 | 
			
		||||
	void photoProgress(const FullMsgId &msgId);
 | 
			
		||||
	void documentProgress(const FullMsgId &msgId);
 | 
			
		||||
 | 
			
		||||
	void photoFailed(const FullMsgId &msgId);
 | 
			
		||||
	void documentFailed(const FullMsgId &msgId);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	struct File;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,6 +114,17 @@ private:
 | 
			
		|||
	std::map<FullMsgId, File> uploaded;
 | 
			
		||||
	QTimer nextTimer, stopSessionsTimer;
 | 
			
		||||
 | 
			
		||||
	rpl::event_stream<UploadedPhoto> _photoReady;
 | 
			
		||||
	rpl::event_stream<UploadedDocument> _documentReady;
 | 
			
		||||
	rpl::event_stream<UploadedThumbDocument> _thumbDocumentReady;
 | 
			
		||||
	rpl::event_stream<UploadedSecure> _secureReady;
 | 
			
		||||
	rpl::event_stream<FullMsgId> _photoProgress;
 | 
			
		||||
	rpl::event_stream<FullMsgId> _documentProgress;
 | 
			
		||||
	rpl::event_stream<FullMsgId> _secureProgress;
 | 
			
		||||
	rpl::event_stream<FullMsgId> _photoFailed;
 | 
			
		||||
	rpl::event_stream<FullMsgId> _documentFailed;
 | 
			
		||||
	rpl::event_stream<FullMsgId> _secureFailed;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace Storage
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ enum class SendMediaType {
 | 
			
		|||
	Photo,
 | 
			
		||||
	Audio,
 | 
			
		||||
	File,
 | 
			
		||||
	Secure,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SendMediaPrepare {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue